<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Athrunen.dev - Tech Blog by Malte Vrampe]]></title><description><![CDATA[The personal tech blog of Malte Vrampe aka Athrunen]]></description><link>https://blog.athrunen.dev/</link><image><url>https://blog.athrunen.dev/favicon.png</url><title>Athrunen.dev - Tech Blog by Malte Vrampe</title><link>https://blog.athrunen.dev/</link></image><generator>Ghost 5.2</generator><lastBuildDate>Sun, 01 Mar 2026 13:17:20 GMT</lastBuildDate><atom:link href="https://blog.athrunen.dev/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[You're not original and neither is anyone else]]></title><description><![CDATA[My views on creating original ideas, why it doesn't matter that nothing is original and how you can still achieve some originality.]]></description><link>https://blog.athrunen.dev/youre-not-original-and-neither-is-anyone-else/</link><guid isPermaLink="false">60e60581e8ffee0001812e7c</guid><category><![CDATA[Opinion]]></category><dc:creator><![CDATA[Malte Vrampe]]></dc:creator><pubDate>Fri, 16 Jul 2021 23:27:16 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1594377157609-5c996118ac7f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDYxfHxjcmVhdGl2ZXxlbnwwfHx8fDE2MjU2ODgzMjk&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<h2 id="1-nobody-is-original"><br>1. Nobody is original<br></h2><img src="https://images.unsplash.com/photo-1594377157609-5c996118ac7f?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MnwxMTc3M3wwfDF8c2VhcmNofDYxfHxjcmVhdGl2ZXxlbnwwfHx8fDE2MjU2ODgzMjk&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" alt="You&apos;re not original and neither is anyone else"><p>But that does not matter, nobody cares.</p><ul><li>Not the market as it&apos;s big enough and everybody does it, unless somebody dumps.</li><li>Not the users as more competition is good for them, unless it&apos;s garbage.</li><li>Not the competitors as they have better things to do and rather focus on themselves, unless their patents or copyrights are infringed. Also, trade secrets are weird...<sup><a href="#1">[1]</a></sup></li></ul><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/Screenshot-2021-07-10-02-02-42-2.png" width="1184" height="1073" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/Screenshot-2021-07-10-02-02-42-2.png 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/Screenshot-2021-07-10-02-02-42-2.png 1000w, https://blog.athrunen.dev/content/images/2021/07/Screenshot-2021-07-10-02-02-42-2.png 1184w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>I mean, why should there be more than one brand per type of cable?</figcaption></figure><p>People will buy/consume/use stuff for a lot of reasons unconnected to originality:</p><ul><li>Because it&apos;s cheaper</li><li>Because you&apos;re not the competitor</li><li>Because you&apos;re local</li><li>Because you&apos;re smaller</li><li>Because you&apos;re on another platform</li></ul><p>Or simply because they did not bother to look any further. <br>I mean, when was the last time you strayed from the safety of the first page of results? </p><p>So even just being placed better than someone else &#x2014; whether out of luck, some kind of business practice or a gruesome sacrifice to a god &#x2014; matters more than how original your work is.</p><p>The economic human being, the Homo economicus<sup><a href="#2">[2]</a></sup>, does not exist.</p><hr><h2 id="2-aim-for-originality-in-both-process-and-execution">2. Aim for originality in both process and execution<br></h2><p>While today you will seldom find something completely new, there are always ideas that mix and match old ones together in new ways. <br>Therefore I would argue the combination and remixing of something that already exists CAN be called an act of originality. So, there is a distinction to be made between original and originality.</p><hr><h3 id="21-but-where-do-we-find-those-new-andor-improved-ideas">2.1 But where do we find those new and/or improved ideas?<br></h3><p>There is a saying, that innovation happens at the intersection of two or more fields.<br>So when you create a patchwork of ideas<a href="#3"><sup>[3]</sup></a> and recycle existing ideas you are innovating.<br>You take a lot of ideas someone else already created and made something cohesive and useful out of it, basically something worth more than the sum of their parts. What you combine and in what ways, that is your act of originality.</p><p>That means you are less of a creator and more a mad alchemist combining various substances until the philosopher&apos;s stone plops out and you have to secure it behind a big three-headed dog with a very inconspicuous name.<sup><a href="#4">[4]</a></sup></p><p>And even if two products are the same, the way they have been derived &#x2014; the nuances between the processes &#x2014; gives them their originality.</p><hr><h3 id="21-look-out-into-the-world">2.1 Look out into the world<br></h3><p>I mean, take a look at games. </p><p>All those different multiplayer shooters, some may truly be exploring some interesting combinations of ideas, but others are more down to earth &#x2014; you pull the trigger, bullets come shooting &#x2014; they&apos;re all quite similar yet have taken different ways to the same goal.</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/fitting_screenshot.jpg" width="1236" height="584" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/fitting_screenshot.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/fitting_screenshot.jpg 1000w, https://blog.athrunen.dev/content/images/2021/07/fitting_screenshot.jpg 1236w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/20210707232308_1.jpg" width="1920" height="1080" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/20210707232308_1.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/20210707232308_1.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2021/07/20210707232308_1.jpg 1600w, https://blog.athrunen.dev/content/images/2021/07/20210707232308_1.jpg 1920w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/20210708002200_1.jpg" width="1920" height="1080" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/20210708002200_1.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/20210708002200_1.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2021/07/20210708002200_1.jpg 1600w, https://blog.athrunen.dev/content/images/2021/07/20210708002200_1.jpg 1920w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>EVE, No Man&apos;s Sky and Elite Dangerous</figcaption></figure><p>Or if you take a look at those different space games up there, wouldn&apos;t you say EVE, No Man&apos;s Sky and Elite Dangerous are quite different, even though they have massive overlap when it comes to more general features?</p><p>Each one of them lures in a slightly different kind of user; has its own slightly different kind of market.</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/ss_39c531e1831491b2140a3bdf36cf70ee342a1e6d.1920x1080.jpg" width="1920" height="1080" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/ss_39c531e1831491b2140a3bdf36cf70ee342a1e6d.1920x1080.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/ss_39c531e1831491b2140a3bdf36cf70ee342a1e6d.1920x1080.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2021/07/ss_39c531e1831491b2140a3bdf36cf70ee342a1e6d.1920x1080.jpg 1600w, https://blog.athrunen.dev/content/images/2021/07/ss_39c531e1831491b2140a3bdf36cf70ee342a1e6d.1920x1080.jpg 1920w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/ss_4a0424f7045cc8a0c82d796130ad74bae7b9277a.1920x1080.jpg" width="1920" height="1080" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/ss_4a0424f7045cc8a0c82d796130ad74bae7b9277a.1920x1080.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/ss_4a0424f7045cc8a0c82d796130ad74bae7b9277a.1920x1080.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2021/07/ss_4a0424f7045cc8a0c82d796130ad74bae7b9277a.1920x1080.jpg 1600w, https://blog.athrunen.dev/content/images/2021/07/ss_4a0424f7045cc8a0c82d796130ad74bae7b9277a.1920x1080.jpg 1920w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/ss_584788d0004933fb10420fe9284515e6f7be577f.1920x1080.jpg" width="1920" height="1080" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/ss_584788d0004933fb10420fe9284515e6f7be577f.1920x1080.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/ss_584788d0004933fb10420fe9284515e6f7be577f.1920x1080.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2021/07/ss_584788d0004933fb10420fe9284515e6f7be577f.1920x1080.jpg 1600w, https://blog.athrunen.dev/content/images/2021/07/ss_584788d0004933fb10420fe9284515e6f7be577f.1920x1080.jpg 1920w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>BPM, Necrodancer and Sea of Thieves</figcaption></figure><p>But there are some games out there that are exploring very interesting combinations. </p><p>BPM and Necrodancer are interesting for both rhythm gamers and fans of classical shooters or dungeon-crawlers respectively, while Sea of thieves makes being a pirate a multiplayer experience for entire groups of friends.</p><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/lotr_dwarfes.jpg" width="2000" height="821" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/lotr_dwarfes.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/lotr_dwarfes.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2021/07/lotr_dwarfes.jpg 1600w, https://blog.athrunen.dev/content/images/size/w2400/2021/07/lotr_dwarfes.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/seven-dwarfs.jpg" width="2000" height="1250" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/seven-dwarfs.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/seven-dwarfs.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2021/07/seven-dwarfs.jpg 1600w, https://blog.athrunen.dev/content/images/size/w2400/2021/07/seven-dwarfs.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2021/07/ss_ed6a44942d80613b16e2312fd6c5263d35bfd1ba.1920x1080_cut_blur.jpg" width="1153" height="1078" loading="lazy" alt="You&apos;re not original and neither is anyone else" srcset="https://blog.athrunen.dev/content/images/size/w600/2021/07/ss_ed6a44942d80613b16e2312fd6c5263d35bfd1ba.1920x1080_cut_blur.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2021/07/ss_ed6a44942d80613b16e2312fd6c5263d35bfd1ba.1920x1080_cut_blur.jpg 1000w, https://blog.athrunen.dev/content/images/2021/07/ss_ed6a44942d80613b16e2312fd6c5263d35bfd1ba.1920x1080_cut_blur.jpg 1153w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>Various depictions of dwarfs(The Hobbit, Snow White and the Seven Dwarfs, Warhammer: Vermintide 2)</figcaption></figure><p>Even most works of fiction today build on top of age-old ideas. </p><p>The fables and tales of the olden days. Elves, Gnomes, Dwarfs. Even angels, demons and daemons<sup><a href="#5">[5]</a></sup>. <br>Some stories, like fanfictions or derivative works, reuse parts of the same world to tell entirely different stories. Exploring the yet untold stories of a world that already exists.</p><p>And a lot of people enjoy diving into stories that remind them of those they already read. They have a favorite genre. They want similar stories exploring different twists that create an entirely different experience.</p><p>And then there&apos;s&#x2026; well&#x2026; ever heard of Warhammer<sup><a href="#6">[6]</a></sup>?</p><hr><h2 id="3-final-words">3. Final words<br></h2><p>Oh well, my focus seems to have drifted rather heavily into the creative realm.</p><p>Nevertheless, my point still stands.</p><p>There might be nothing original left to create, but many patchworks of ideas are still undiscovered.<br>And even if a very similar one already exists, remember, no patchwork looks quite the same.</p><p>So just create what you want to create. <br>Will it work out? Well, you&apos;ll never know without trying first.</p><hr><!--kg-card-begin: html--><p>
    1.<a id="1">Look up the legality of trade-secret reverse engineering, it&apos;s wild</a>
    <br>
    2.<a id="2" href="https://en.wikipedia.org/wiki/Homo_economicus" target="_blank">https://en.wikipedia.org/wiki/Homo_economicus</a>
    <br>
    3.<a id="3" href="https://en.wikipedia.org/wiki/Personal_knowledge_management" target="_blank">https://en.wikipedia.org/wiki/Personal_knowledge_management</a>
    <br>
    4.<a id="4" href="https://en.wikipedia.org/wiki/Daimon" target="_blank">https://en.wikipedia.org/wiki/Daimon</a>
    <br>
    5.<a id="5">You&apos;re a wizard &apos;arry</a>
    <br>
    6.<a id="6" href="https://en.wikipedia.org/wiki/Warhammer_40,000#Spin-off_games,_novels,_and_other_media" target="_blank">https://en.wikipedia.org/wiki/Warhammer_40,000#Spin-off_games,_novels,_and_other_media</a>
</p><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[LEDs and the magic of 16 Bit]]></title><description><![CDATA[An easy upgrade for most embedded systems that allows you to get a massive increase in color fidelity, dimming control, and more.]]></description><link>https://blog.athrunen.dev/16-bit-magic/</link><guid isPermaLink="false">5f3402f3b9d58d0001a07304</guid><category><![CDATA[Embedded programming]]></category><dc:creator><![CDATA[Malte Vrampe]]></dc:creator><pubDate>Sun, 22 Nov 2020 22:17:00 GMT</pubDate><media:content url="https://blog.athrunen.dev/content/images/2020/11/Gradientcompare.png" medium="image"/><content:encoded><![CDATA[<h3 id="join-the-light-side-and-never-come-back-">Join the light side and never come back.</h3><img src="https://blog.athrunen.dev/content/images/2020/11/Gradientcompare.png" alt="LEDs and the magic of 16 Bit"><p>Today I want to showcase an easy upgrade applicable to most embedded devices that greatly increases the quality of your RGB(W) LEDs color output.</p><hr><h2 id="pwm-the-basics">PWM - The basics</h2><p>When first learning about controlling LEDs via, for example, Arduino, you will be introduced to <strong>PWM</strong>, <strong>P</strong>ulse <strong>W</strong>idth <strong>M</strong>odulation, a technique to get the effect of dimming without actually changing the resistance in the circuit to get a specific voltage.</p><p>It tricks our eyes into thinking the light is at a specific brightness by turning it on and off very quickly(<strong>P</strong>ulse) within a time-frame where the <strong>W</strong>idth of the changing signal(<strong>M</strong>odulation) is proportional to the amount of brightness wanted.</p><blockquote>This also circumvents the problem of the differences in emission between LEDs(even if they are of the same color/type) which are especially large in the low-power range.<sup><a href="#2">[2]</a></sup> But as that is out of the scope of this article, check out the corresponding source for more information on that.</blockquote><p>Take a look at the following two graphs showcasing two different <strong>duty cycles</strong>, one at 50% and one at 75%. The duty cycle of a PWM signal describes the amount of time it is turned on within a specific time-frame and is therefore roughly equivalent to the brightness of a connected LED.</p><!--kg-card-begin: html--><canvas id="myChart" width="400" height="150" style="min-height:150px"></canvas>
<script>
var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, {
    type: 'scatter',
	data: {
        datasets: [{
            label: 'power level',
            steppedLine: true,
            data: [
                {
                    x: 0,
                    y: 1
                },
                {
                    x: 0.25,
                    y: 1
                },
                {
                    x: 0.5,
                    y: 0
                },
                {
                    x: 0.75,
                    y: 0
                },
                {
                    x: 1,
                    y: 1
                },
                {
                    x: 1.5,
                    y: 0
                },
                {
                    x: 2,
                    y: 1
                },
                {
                    x: 2.5,
                    y: 0
                },
                {
                    x: 3,
                    y: 1
                },
                {
                    x: 3.5,
                    y: 0
                },
                {
                    x: 4,
                    y: 0
                }
            ],
            borderColor: 'rgb(139, 92, 203)',
            pointRadius: 0,
            fill: false,
            showLine: true,
            datalabels: {
                labels: {
                    title: null,
                    value: null,
                    amount_on: {
                    	display: function(context) {
                        	return context.dataIndex === 1
                        },
                        formatter: function(value, context) {
                    		return '50% on'
                		},
                        anchor: 'start',
                        align: 'bottom',
                        textAlign: 'center',
                        offset: '8',
                        borderRadius: '5',
                        padding: {
                        	top: 2,
                            bottom: 1,
                            left: 3,
                            right: 3
                        },
                        backgroundColor: '#76CC52',
                        color: '#212a2e',
                        font: function(context) {
                            var width = context.chart.width;
                            var size = Math.round(width / 64);
                            return {
                                size: size,
                                weight: 600
                            };
                        }
                    },
                    amount_off: {
                    	display: function(context) {
                        	return context.dataIndex === 3
                        },
                        formatter: function(value, context) {
                    		return '50% off'
                		},
                        anchor: 'start',
                        align: 'top',
                        textAlign: 'center',
                        offset: '8',
                        borderRadius: '5',
                        padding: {
                        	top: 2,
                            bottom: 1,
                            left: 3,
                            right: 3
                        },
                        backgroundColor: '#DE3526',
                        color: '#212a2e',
                        font: function(context) {
                            var width = context.chart.width;
                            var size = Math.round(width / 64);
                            return {
                                size: size,
                                weight: 600
                            };
                        }
                    }
                }
            }
        }]
    },
    options: {
        responsive: true,
        title: {
        	display: true,
            text: 'PWM - 50%',
        },
        legend: {
        	display: true,
        },
        scales: {
            yAxes: [{
                display: true,
                ticks: {
                    min: -0.25,
                    max: 1.25,
                    stepSize: 1,
                    callback: function(dataLabel, index) {
            			if(dataLabel === 0)
                        	return 'LOW'
                        if(dataLabel === 1)
                        	return 'HIGH'
                        return ''
          			}
                }
            }],
            xAxes: [{
                display: false,
                type: 'linear',
                ticks: {
                    stepSize: 1,
                }
            }],
        },
        annotation: {
            drawTime: 'afterDraw',
            annotations: [
                {
                    type: "line",
                    mode: "vertical",
                    scaleID: "x-axis-1",
                    value: 1,
                    borderColor: "#b2c0cb",
                    borderWidth: 1,
                },
                {
                    type: "line",
                    mode: "vertical",
                    scaleID: "x-axis-1",
                    value: 2,
                    borderColor: "#b2c0cb",
                    borderWidth: 1,
              	},
                {
                    type: "line",
                    mode: "vertical",
                    scaleID: "x-axis-1",
                    value: 3,
                    borderColor: "#b2c0cb",
                    borderWidth: 1,
              	},
            ]
      	}
    }
});
</script><!--kg-card-end: html--><hr><!--kg-card-begin: html--><canvas id="myChart1" width="400" height="150" style="min-height:150px"></canvas>
<script>
var ctx = document.getElementById('myChart1').getContext('2d');
var myChart1 = new Chart(ctx, {
    type: 'scatter',
	data: {
        datasets: [{
            label: 'power level',
            steppedLine: true,
            data: [
                {
                	x: 0,
                	y: 1
                },
                {
                	x: 0.375,
                	y: 1
                },
                {
                 	x: 0.75,
                 	y: 0
                },
                {
                 	x: 0.875,
                 	y: 0
                },
                {
                 	x: 1,
                 	y: 1
                },
                {
                 	x: 1.75,
                 	y: 0
                },
                {
                	x: 2,
                	y: 1
                },
                {
                 	x: 2.75,
                 	y: 0
                },
                {
                 	x: 3,
                 	y: 1
                },
                {
                 	x: 3.75,
                 	y: 0
                },
                {
                    x: 4,
                    y: 0
                }
            ],
            borderColor: 'rgb(139, 92, 203)',
            pointRadius: 0,
            fill: false,
            showLine: true,
            datalabels: {
                labels: {
                    title: null,
                    value: null,
                    amount_on: {
                    	display: function(context) {
                        	return context.dataIndex === 1
                        },
                        formatter: function(value, context) {
                    		return '75% on'
                		},
                        anchor: 'start',
                        align: 'bottom',
                        textAlign: 'center',
                        offset: '8',
                        borderRadius: '5',
                        padding: {
                        	top: 2,
                            bottom: 1,
                            left: 3,
                            right: 3
                        },
                        backgroundColor: '#76CC52',
                        color: '#212a2e',
                        font: function(context) {
                            var width = context.chart.width;
                            var size = Math.round(width / 64);
                            return {
                                size: size,
                                weight: 600
                            };
                        }
                    },
                    amount_off: {
                    	display: function(context) {
                        	return context.dataIndex === 3
                        },
                        formatter: function(value, context) {
                    		return '25% \noff'
                		},
                        anchor: 'start',
                        align: 'top',
                        textAlign: 'center',
                        offset: '8',
                        borderRadius: '5',
                        padding: {
                        	top: 2,
                            bottom: 1,
                            left: 3,
                            right: 3
                        },
                        backgroundColor: '#DE3526',
                        color: '#212a2e',
                        font: function(context) {
                            var width = context.chart.width;
                            var size = Math.round(width / 64);
                            return {
                                size: size,
                                weight: 600
                            };
                        }
                    }
                }
            }
        }]
    },
    options: {
        responsive: true,
        title: {
            display: true,
            text: 'PWM - 75%',
        },
        scales: {
            yAxes: [{
                display: true,
                ticks: {
                    min: -0.35,
                    max: 1.35,
                    stepSize: 1,
                    callback: function(dataLabel, index) {
            			if(dataLabel === 0)
                        	return 'LOW'
                        if(dataLabel === 1)
                        	return 'HIGH'
                        return ''
                    }
                }
            }],
            xAxes: [{
                display: false,
                type: 'linear',
                ticks: {
                    stepSize: 1,
                }
            }],
        },
        annotation: {
            drawTime: 'afterDraw',
            annotations: [
                {
                    type: "line",
                    mode: "vertical",
                    scaleID: "x-axis-1",
                    value: 1,
                    borderColor: "#b2c0cb",
                    borderWidth: 1,
                },
                {
                    type: "line",
                    mode: "vertical",
                    scaleID: "x-axis-1",
                    value: 2,
                    borderColor: "#b2c0cb",
                    borderWidth: 1,
              	},
                {
                    type: "line",
                    mode: "vertical",
                    scaleID: "x-axis-1",
                    value: 3,
                    borderColor: "#b2c0cb",
                    borderWidth: 1,
              	},
            ]
      	}
    }
});
</script><!--kg-card-end: html--><p>This time-frame is described by the <strong>frequency</strong> of the PWM signal, as the time it takes for the signal to turn on, off, and on again. Most of the time you will find this represented in the number of repetitions within a second, otherwise known as <strong>Hertz</strong>.<br>The size of the frequency has some influence on the artifacts you might encounter. But more on that later.</p><p>Within a time-frame, you could theoretically have an infinite amount of different duty cycles. <strong>But that is not the case.</strong><br><br>We are sadly limited by physics, the devices we use, and our configuration.<br>And we are here for the last one of those.</p><p>The amount of Granularity that can be archived with the duty cycle in a given PWM System is controlled by its <strong>resolution</strong>. Which is either represented with its size in <strong>bits</strong> or the <strong>number of steps</strong> possible.</p><p>When working with PWM to control light you most certainly worked with <strong>8 bits</strong> in form of a value between 0 and 255. This is the default for most systems and tutorials.</p><p>Yet most of those systems support even higher bit-rates with almost no drawbacks. In this case, I have chosen <strong>16 bits</strong> for its high support, only minimal drawbacks, and because it is right at the edge where the increase of benefits becomes less noticeable with each increase in resolution.</p><hr><h2 id="massive-increase-in-steps">Massive increase in steps</h2><p>An increase in resolution allows us to have a lot finer dimming control of the different LEDs, as we have now about <strong>65280 more steps</strong> of resolution(an increase of 256%).<br>Which is where the fun begins.</p><p>When it comes to mixing colors you get a <strong>massive increase in color fidelity</strong>, for RGB from 16 Million(256<sup>3</sup>) to 281 Trillion(65536<sup>3</sup>) and for RGBW from 4 Billion(256<sup>4</sup>) to 18 Quintillion(65536<sup>4</sup>).</p><blockquote>Just don&apos;t try to imagine that, it&apos;s a lost cause.</blockquote><p>With this high amount of granularity <strong>dimming without any visible jumps</strong> becomes possible, which is especially <strong>important in the low-power range</strong> as our eyes are more sensitive there.<br>Gamma correction is now also way easier, which is <strong>making the dimming more natural to the eye</strong>.<sup><a href="#1">[1]</a></sup></p><p>For gamma correction to work we are in essence mapping a <strong>quadratic function</strong> - as that is how our eyes(approximately) perceive changes in brightness - to our available granularity. And that is with 65536 steps just way easier(read: results in smaller errors) than mapping it to 256 steps.</p><p>The following two graphs show just that:</p><!--kg-card-begin: html--><canvas id="myChart3" width="400" height="400" style="min-height:150px"></canvas>
<script>
var data = []
for (var i in [...Array(256).keys()]) {
    var o = i / 255
    data.push({
        x: o,
        y: Math.round(Math.pow(o, 2) * 255) / 255
    })
}
console.log(data)
var ctx = document.getElementById('myChart3').getContext('2d');
var myChart3 = new Chart(ctx, {
    type: 'scatter',
	data: {
        datasets: [{
            label: 'power level',
            steppedLine: false,
            data: data,
            borderColor: 'rgb(139, 92, 203)',
            pointRadius: 0.5,
            fill: false,
            showLine: false,
            datalabels: {
                labels: {
                    title: null,
                    value: null,
                }
            }
        }]
    },
    options: {
        responsive: true,
        title: {
        	display: true,
            text: 'Quadratic approximation - 8 bit',
        },
        legend: {
        	display: true,
        },
        plugins: {
            zoom: {
                pan: {
                    enabled: true,
                    mode: 'xy'
                },
                zoom: {
                    enabled: true,
                    mode: 'xy'
                }
            }
        },
    }
});
</script><!--kg-card-end: html--><hr><!--kg-card-begin: html--><canvas id="myChart4" width="400" height="400" style="min-height:150px"></canvas>
<script>
var data = []
for (var i in [...Array(65536).keys()]) {
    var o = i / 65535
    data.push({
        x: o,
        y: Math.round(Math.pow(o, 2) * 65535) / 65535
    })
}
console.log(data)
var ctx = document.getElementById('myChart4').getContext('2d');
var myChart4 = new Chart(ctx, {
    type: 'scatter',
	data: {
        datasets: [{
            label: 'power level',
            steppedLine: false,
            data: data,
            borderColor: 'rgb(139, 92, 203)',
            pointRadius: 0.2,
            fill: false,
            showLine: false,
            datalabels: {
                labels: {
                    title: null,
                    value: null,
                }
            }
        }]
    },
    options: {
        responsive: true,
        title: {
        	display: true,
            text: 'Quadratic approximation - 16 bit',
        },
        legend: {
        	display: true,
        },
        plugins: {
            zoom: {
                pan: {
                    enabled: true,
                    mode: 'xy'
                },
                zoom: {
                    enabled: true,
                    mode: 'xy'
                }
            }
        },
        downsample: {
            enabled: true,
            threshold: 2000,
            preferOriginalData: true,
            auto: true,
            restoreOriginalData: false
        }
    }
});
</script><!--kg-card-end: html--><p></p><!--kg-card-begin: html--><button type="button" onclick="myChart4.options.downsample.threshold = myChart4.options.downsample.threshold == 2000 ? 65536 : 2000">Toggle down-sampling</button><!--kg-card-end: html--><p></p><blockquote>Feel free to zoom in<br>16 bit version is down-sampled to not overwhelm your browser<br>To toggle, press the above button and zoom again</blockquote><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2020/11/HSV_cone.png" class="kg-image" alt="LEDs and the magic of 16 Bit" loading="lazy"><figcaption>HSV color space as a cone (Source: (3ucky(3all [<a href="http://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA</a>])</figcaption></figure><p>And last but not least, we can now more precisely convert to other color formats without having huge jumps in the output color and brightness. I find the <strong>Hue-Saturation-*</strong>(HS*) family of formats to be a particularly good starting point.</p><p>You might know, that when you mix RGB(and W) colors you traverse the <strong>area of a cube</strong> along the axis R, G and B. From a technical standpoint, this is the optimal way to control an RGB(W) system, but when it comes to humans most of us do not have internalized color mixing in this way.<br><br>For us, a <strong>color wheel</strong> is a much better option, in combination with a way to control the amount of brightness and color(otherwise known as saturation).<br>In the case of the HS* family, we traverse either a <strong>cylinder, cone or <a href="https://en.wikipedia.org/wiki/Bicone">bicone</a></strong>, where the <strong>rotation around the surface</strong> changes the <strong>Hue</strong>, <strong>traversing inwards</strong> changes the <strong>saturation</strong> and <strong>moving up/down</strong> changes the <strong>brightness</strong>.</p><p>The star in the name depends on the method chosen to calculate the brightness. And there are a lot of different methods, just find one that suits your needs. For more information about that you might wanna <strong>check out my stuff <a href="https://blog.athrunen.dev/recycling-old-display-backlight-as-daylight-lamps-or-softboxes/">here</a> as well as the awesome material at <a href="https://blog.saikoled.com/">SaikoLED</a></strong>, which is where I get a lot of my references from.</p><hr><h2 id="the-thing-about-the-frequency">The thing about the frequency</h2><p>While there are some small obvious drawbacks like more use of <strong>CPU time</strong> and the need for <strong>bigger data types</strong> there is also one that I hinted at with the frequency.</p><p>As our device needs more time for <strong>higher resolutions</strong> to calculate and transmit them, this, in turn, <strong>decreases the maximum frequency</strong> that can be reached the higher in resolution we go.<br><br>That means with a frequency low enough you will be able to <strong>see the light flickering</strong>. But even before we reach that point this effect will <strong>show up on cameras</strong> as either flickering or visible color bands as the light is changing. <br>And not only cameras are able to notice this rapid flickering. Because even though we may not be able to consciously register it, it still affects us by inducing <strong>eye strain at lower frequencies</strong>.<a href="#4"><sup>[4]</sup></a></p><p>And while I was not able to find a specific frequency for which this is true, the article in the footnote claims that <strong>anything above 400Hz should be fine</strong>. Which shouldn&apos;t be a problem with most devices. In case of the ESP32 you can calculate the maximum frequency of a given resolution like this:</p><p>$$\frac{timer\_clock}{2^{resolution}} \Rightarrow \frac{80\verb|MHz|}{2^{16}} \approx 1220\verb|Hz|$$</p><p>And last but not least, to fix the flickering issue with cameras you can simply <strong>adjust your shutter speed to be a multiple of the frequency</strong> of your LEDs.</p><p>All in all, I find the upgrade to 16 bits to be a seamless one, with a <strong>great increase in color fidelity and dimming resolution</strong> that leads to <strong>easier to control colors</strong> and the ability to <strong>correct for the human vision</strong>. And the drawbacks are for most use-cases, to put it frankly, <strong>non-existent</strong>.</p><hr><h2 id="summary">Summary</h2><p><strong>Thank you for reading!</strong> If you liked this article please feel free to take a look around, like and comment and maybe even share it. </p><h3 id="benefits">Benefits</h3><ul><li>Smoother gradients</li><li>Finer color control especially in the low-level range</li><li>- &gt; As our eyes are quite sensitive there and jumps are quite noticeable</li><li>Better color correction -&gt; quadratic mapping</li><li>Allows HS* color formats to work more effectively</li><li>Easy upgrade already supported by most devices</li></ul><h3 id="costs">Costs</h3><ul><li>General overhead</li><li>the decrease in frequency that comes with an increase in resolution might cause visual artifacts as well as eye strain if the frequency is too low</li></ul><hr><p>Sources:</p><!--kg-card-begin: html--><p>
    1.<a id="1" href="https://blog.saikoled.com/post/45630908157/implementing-arbitrary-color-correction-with-the" target="_blank"> https://blog.saikoled.com/post/45630908157/implementing-arbitrary-color-correction-with-the</a>
    <br>
    2.<a id="2" href="https://www.instyleled.co.uk/8bit-vs-16bit-led-control/" target="_blank"> https://www.instyleled.co.uk/8bit-vs-16bit-led-control/</a>
    <br>
    3.<a id="3" href="https://www.mothergrid.de/fachwissen/dimming-led-lights-basics-and-difficulties/" target="_blank"> https://www.mothergrid.de/fachwissen/dimming-led-lights-basics-and-difficulties/</a>
    4.<a id="4" href="https://www.led-professional.com/resources-1/articles/flicker-beyond-perception-limits-a-summary-of-lesser-known-research-results" target="_blank">https://www.led-professional.com/resources-1/articles/flicker-beyond-perception-limits-a-summary-of-lesser-known-research-results</a>
</p><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[MIDI controlled RGBW strips]]></title><description><![CDATA[How to control LEDs with a MIDI Keyboard, setup a plug and play WiFi system, announcing a device in the network, and building a simple command server.]]></description><link>https://blog.athrunen.dev/midi-controlled-rgbw-strips/</link><guid isPermaLink="false">5e55f27ba547d80001edb2c5</guid><category><![CDATA[Embedded programming]]></category><category><![CDATA[Python]]></category><category><![CDATA[C++]]></category><dc:creator><![CDATA[Malte Vrampe]]></dc:creator><pubDate>Sun, 10 May 2020 23:42:00 GMT</pubDate><media:content url="https://blog.athrunen.dev/content/images/2022/06/ezgif.com-gif-maker-1.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.athrunen.dev/content/images/2022/06/ezgif.com-gif-maker-1.webp" alt="MIDI controlled RGBW strips"><p>For the last few weeks, I played around with controlling my LEDs over the network by mapping a MIDI keyboards dials to each of my color controllers settings.</p><p>Now I want to share my results and the code with you as well as some of the things I learned along the way.</p><hr><p>Full HD version of the video above:</p><figure class="kg-card kg-embed-card"><iframe width="480" height="270" src="https://www.youtube.com/embed/9XJQ-Dc_FAk?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></figure><hr><p>In this article, I will give an overview of the client software written in python, showcase a plug and play WiFi setup, how to announce and find the device in the network, and the basic principles of how to get an ESP32 to accept and act out some commands.</p><p>If you want to know more about the basic setup for the RGBW controller, check out <a href="https://blog.athrunen.dev/recycling-old-display-backlight-as-daylight-lamps-or-softboxes/">this article</a>.</p><p>And if you want to try this out yourself consider using <a href="https://amzn.to/2WOcrEc">this</a> amazon affiliate link to get the same keyboard I have and help me pay my bills.</p><hr><h2 id="1-the-client">1. The Client</h2><h3 id="1-1-reading-data-from-the-keyboard">1.1 Reading data from the keyboard</h3><p></p><p>To actually have something to send we need to have a client that reads the MIDI data, transforms it, and generates the command for the server.</p><p>We&apos;ll be reading the incoming data by <s>parsing the incoming USB data and converting it into usable MIDI data</s> using the <code>midi</code> subpackage of <code>pygame</code>.</p><p>I will be using <code>pipenv</code> for easier package management:</p><pre><code class="language-bash"># Install pipenv
pip install --user --upgrade pipenv
# Install pygame
pipenv install pygame
# Enter virtualenv
pipenv shell</code></pre><p>Now we can have the <code>pygame.midi</code> package print us out all midi devices:</p><pre><code class="language-python">import pygame.midi

def get_devices():
    devices = []
    for n in range(pygame.midi.get_count()):
        info = pygame.midi.get_device_info(n)
        if (info[2]): # Check if the device has the input flag
            print (n,info[1]) # (device id, device name)
            devices.append(n)
    return devices

pygame.midi.init()
print(get_devices())</code></pre><p>We will then connect to the MIDI device of our choosing by its device id. Listening in, we will ignore any key-presses and only work with the dials and button presses:</p><pre><code class="language-python">buttons = [*range(16, 23 + 1), 64]
dials = [*range(1, 8 + 1)]

def readInput(input_device):
    while True:
        if input_device.poll():
            event = input_device.read(1)[0][0] # Strip away irrelevant data
            if (event[0] != 176): # Check if this is a change event for a mode/control (this is device channel 1 only, you might want to check 176 to 191)
               	if (debug or event[0] not in [144,]):
                    print (event)
                continue
            if (event[1] in dials): # Check if it&apos;s a dial
                dial_handler(event)
            elif (event[1] in buttons): # Check if it&apos;s a button
                button_handler(event)
            else:
                print (f&quot;ID: {event[1]}, State: {round(event[2]/127,2)}&quot;)
        send_data()


my_input = pygame.midi.Input(device_id)
try:
    readInput(my_input)
except KeyboardInterrupt:
    my_input.close()
</code></pre><p>Let&apos;s take a look at the handler for the dials:</p><pre><code class="language-python">dialdata = {}

def dial_handler(event):
    pos = event[2] / 127 # Remapping to 0 to 1 
    print(f&quot;Dial {event[1]} at position {round(pos, 10)}&quot;) # Mandatory print statement
    dialdata[event[1]] = pos # Take a guess.. event[1] is the dial id</code></pre><p>Skipping the button code as it is only relevant for switching the color mode which is irrelevant for this article. Also removed some code that uses the second row of the dials for fine tuning purposes, for the same reason.</p><h3 id="1-2-compiling-data-to-be-send">1.2 Compiling data to be send</h3><p></p><p>The <code>send_data</code> function will now take the <code>dialdata</code>, transform it, and finally send it to the server:</p><pre><code class="language-python">stime = 0
last_fill = [-1, -1, -1, -1]

def send_data():
    global stime # I know I know global vars are ugly but this is not clean code 101
    global last_fill
    ptime = int(round(time.time() * 1000)) - stime
    if (cm and ptime &gt; 10): # cm and ptime will be explained below
        fill = [-1, -1, -1, -1] # -1 represents a &quot;keep value&quot; state
        for i in range(1, 4 + 1): # Just filling the array with our dialdata
            fdata = dialdata.get(i, -1)
            if (fdata == -1):
                continue
            # I thought here was some fine tuning code somewhere...
            fill[i - 1] = fdata
        if (fill == last_fill): # Why would we send a new command if nothing changed?
            return
        last_fill = fill
        f = fill
        m = &quot;manual&quot; # For simplicity sake, check the source code for more interesting stuff
        bridge.send_command(cm, &quot;set&quot;, [m, *f]) # &quot;set&quot; and the m, *f array are just the type of the command and it&apos;s respective data.. more on that later
        stime = int(round(time.time() * 1000))
</code></pre><p>First of all, what is this <code>stime</code> and what is it doing there? <br>Well, to not overwhelm the ESP by directly sending the status of the MIDI dials to it, causing it to queue up commands that will be overwritten by the newest one anyway, <br>we will have to limit the number of times we actually send the data. </p><p>In my case, I will wait 10 ms before sending the next command. <br>Which is <em>coincidentally</em> about the same time the ESP takes to update its color and become ready for the next command.</p><p>To be able to understand how we are sending data we need to understand what a command is first.</p><p>It is an identifier for the action that should be performed, followed by its respective options. It is then sent over a TCP connection to the server. <br>A command to turn on all LEDs looks like this: </p><pre><code class="language-nohighlight">set manual 1 1 1 1 \n
&#x2502;   &#x2502;      &#x2502; &#x2502; &#x2502; &#x2502;
&#x2502;   &#x2502;      &#x2502; &#x2502; &#x2502; &#x2514;&gt; White value
&#x2502;   &#x2502;      &#x2502; &#x2502; &#x2514;&gt; Blue value
&#x2502;   &#x2502;      &#x2502; &#x2514;&gt; Green value
&#x2502;   &#x2502;      &#x2514;&gt; Red value
&#x2502;   &#x2514;&gt; how should the parameters be interpreted?
&#x2514;&gt; set color to ...</code></pre><p>Note the newline character on the end as it will be essential for performance later on.</p><h3 id="1-3-sending-the-data">1.3 Sending the data</h3><p></p><p>To construct the command from its parameters, encoding it to be sent as bytes and actually sending it we will use this code:</p><pre><code class="language-python">def send_command(socket, command, options):
    socket.send(f&quot;{command} {&apos; &apos;.join(str(x) for x in options)} \n&quot;.encode(&quot;utf-8&quot;))</code></pre><p> But how do we get this socket?</p><p>This socket or <code>cs</code> from the <code>send_data</code> function further up stands for <code>c</code>ommand <code>s</code>ocket and is basically our command line that gets injected into the <code>send_command</code> function to make it stateless and therefore easier to use.</p><p>To open this socket we first have to find out the IP of the ESP. <br>Thankfully the ESP is constantly screaming into the endless void of the network that it indeed exists. (more on that later) And if we are willing to listen to its desperate screams by opening a specific port that it broadcasts to we can get its IP:</p><pre><code class="language-python">from socket import *

def open_listener():
    bclistener = socket(AF_INET, SOCK_DGRAM) # Some setup stuff
    bclistener.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # Some more setup stuff
    bclistener.bind((&apos;0.0.0.0&apos;, 24444)) # Listening on port 24444
    return bclistener

def await_controller(bclistener):
    while True:
        message = bclistener.recvfrom(64) # Reading message from socket
        if (message[0] == b&apos;ThatLEDController online!&apos;): # Checking for specific message
            return message[1][0] # Returning ip


bc = open_listener()
ip = await_controller(bc)

</code></pre><p>Now that we got the IP we can plug that into a socket and open our command socket. Here is a handy function for that:</p><pre><code class="language-python">def open_sender(ip):
    cmsender = socket(AF_INET, SOCK_STREAM) # Some setup
    cmsender.connect((ip, 25555)) # Connecting to port 25555 at &apos;ip&apos;
    return cmsender
    
cs = open_sender(ip)</code></pre><p>Now that the client is implemented, let&apos;s build the server side, shall we?</p><hr><h2 id="2-the-server">2. The Server</h2><h3 id="2-1-connecting-to-the-network">2.1 Connecting to the network</h3><p></p><p>The basic way to connect an ESP32 to the network would be to include the WIFI library, throw some (generally hardcoded) credentials against it and hope it connects: </p><pre><code class="language-c++">#include &lt;WiFi.h&gt;
 
const char* ssid = &quot;SSID&quot;;
const char* password =  &quot;Password&quot;;
 
void setup() {
  Serial.begin(115200);
 
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println(&quot;Connecting to WiFi..&quot;);
  }
 
  Serial.println(&quot;Connected to the WiFi network&quot;);
 
}</code></pre><p>But luckily there is a library called <a href="https://github.com/tzapu/WiFiManager">WiFiManager</a> that does this and much more for us with little to no configuration. It tries to connect to a predefined or saved network and when that fails, opens an AP on its own that that allows you to specify a network that it will then try to connect to:</p><pre><code class="language-c++">#include &lt;WiFiManager.h&gt;
#include &lt;WiFi.h&gt;
#include &lt;WebServer.h&gt;

WiFiManager wifiManager;

void setup() {
  // Configure wifi
  WiFi.mode(WIFI_STA);
  
  // Set dark mode
  wifiManager.setClass(&quot;invert&quot;);
  
  // Set manager to not blocking
  wifiManager.setConfigPortalBlocking(false);
  
  // Automatically connect to saved network
  // or open network named &quot;ThatLEDController&quot;
  // You can also pass this function a predefined network
  wifiManager.autoConnect(&quot;ThatLEDController&quot;); 
}

void loop() {
  // Non blocking process function
  wifiManager.process();
}</code></pre><p>OK, WiFi connection check, now how about allowing the client to actually find us?</p><hr><h3 id="2-2-finding-the-esp">2.2 Finding the ESP</h3><p></p><p>To reliably connect to the device without relying on a hardcoded IP address, we will let the ESP tell it to us on its own. Well, not just us but rather the entire network. &#xA0;</p><p>We will use something called broadcasting for that, that allows you to send some data to a specific IP address in the network(most of the time something with 255 on the end) and it will be relayed to every client connected:</p><pre><code class="language-c++">#include &lt;AsyncUDP.h&gt;
#include &lt;WiFi.h&gt;

// Non blocking wait helper
#define runEvery(t) for (static uint16_t _lasttime;\
                         (uint16_t)((uint16_t)millis() - _lasttime) &gt;= (t);\
                         _lasttime += (t))

AsyncUDP udp;

void setup() {
  // Setup wifi
}

void loop() {
  runEvery(2000) { // Please don&apos;t spam the network
    if (WiFi.status() == WL_CONNECTED) { // Check if we are connected
      IPAddress broadcastip;
      broadcastip = ~WiFi.subnetMask() | WiFi.gatewayIP(); // Get broadcast address for current address range
      if(udp.connect(broadcastip, 24444)) { // Open connection to port 24444
        udp.print(&quot;ThatLEDController online!&quot;); // Send message
      }
      udp.close(); // Close connection again
    }
  }
}</code></pre><p>Moving on to the actual command server.</p><h3 id="2-3-setting-up-a-basic-server">2.3 Setting up a basic server</h3><p></p><p>Here is the basic setup for a simple single client TCP server running on port 25555:</p><pre><code class="language-c++">#include &lt;WiFi.h&gt;

WiFiServer wifiServer(25555); // Open server on port 25555
WifiClient wifiClient;

void setup() {
  // Setup wifi
  
  wifiServer.begin(); // Start server
}

void CheckForConnection() {
  if (wifiServer.hasClient()) { // Check if there is already a client
    if (wifiClient.connected()) { // Check if the client is actually connected
      wifiServer.available().stop(); // Reject new client
    } else {
      wifiClient = wifiServer.available(); // Accept new client
      Serial.println(&quot;Client connected!&quot;);
      wifiClient.write(&quot;Hey there!&quot;); // Send welcome message
    }
  }
}

void loop() {
  CheckForConnection();
}</code></pre><p>You can test that it works using netcat:</p><pre><code class="language-bash">nc 0.0.0.0 25555</code></pre><p>Now there should be a server running that we can extend to accept some commands.</p><hr><h3 id="2-4-receiving-commands">2.4 Receiving commands</h3><p></p><p>First, we will have to read the command into a variable. <br>We will do this with a blocking function, as we have defined the commands to end with a newline which makes it faster than reading it in chunks. Add this part after the connection check:</p><pre><code class="language-c++">if (wifiClient.available()) {
    std::string cm(wifiClient.readStringUntil(&apos;\n&apos;).c_str());
    Serial.println(cm.c_str());
}</code></pre><p>We will now check if the first three characters are actually &quot;set&quot; and if that&apos;s true pass is on to the handler function:</p><pre><code class="language-c++">if (wifiClient.available()) {
    std::string cm(wifiClient.readStringUntil(&apos;\n&apos;).c_str());
    if (cm.rfind(&quot;set &quot;, 0) == 0) {
        setCmd(cm);
    }
}</code></pre><p>The handler function will then split the command at the spaces and check if it is actually long enough. After that the respective values will be extracted and a new color is set:</p><pre><code class="language-c++">std::string mode = &quot;hsv&quot;;
std::array&lt;int, 4&gt; fill = {0, 0, 0, 0};
std::array&lt;std::string, 4&gt; modes = {&quot;manual&quot;, &quot;rgb&quot;, &quot;hsv&quot;, &quot;hsi&quot;};

// Helper function to split string at delimiter
std::vector&lt;std::string&gt; split (const std::string&amp; str, char delimiter) {
  std::vector&lt;std::string&gt; tokens;
  std::string token;
  std::istringstream tokenStream(str);
  while(std::getline(tokenStream, token, delimiter)) {
    tokens.push_back(token);
  }
  return tokens;
}

void setCmd(std::string str) {
  std::vector&lt;std::string&gt; tokens = split(str, &apos; &apos;); // Split string at spaces
  
  if (tokens.size() &lt;= 1) { // Return if the command is to short
    return;
  }
  
  // Get what could be the mode from the extracted tokens
  std::string tmode = tokens[1]; 
  
  // Loop over tokens and extract color, ignore if the data is negative
  // resolution_factor is the pwm range
  int tsize = tokens.size() - 2;
  std::array&lt;int, 4&gt; color;
  for (size_t i = 0; i &lt; 4; i++) {
    color[i] = i &lt; tsize ? atof(tokens[i + 2].c_str()) * config::resolution_factor : fill[i];
    color[i] = color[i] &gt;= 0 ? color[i] : fill[i]; 
  }
  
  // Set mode to extracted mode, if extracted mode is valid
  if (std::find(std::begin(modes), std::end(modes), tmode) != std::end(modes)) {
    mode = tmode;
  }
  
  
  fill = color;
}</code></pre><p>And now there be light.</p><hr><h2 id="3-conclusion-and-a-look-into-the-future">3. Conclusion and a look into the future</h2><p></p><p>For a single device, this setup works great, even though it has some problems with reconnecting to the device. But that can easily be fixed by allowing new devices to override the already connected one.</p><p>I also added a preprocessor flag to my light controller that allows to deactivate anything but the command server, network code, and basic pin setup. This should save some resources when the direct interface is not needed. </p><p>In the long run, however, I want to expand the system to allow for more devices in the same network and create an interface to control them. (<a href="https://www.electronjs.org/">electron</a>-based maybe?)</p><p>Furthermore, I might even try to connect the MIDI keyboard directly to the ESP as it technically acts as a USB host. But having to handle the USB to usable MIDI data conversion myself might be a bit overwhelming.</p><p>Anyway, that&apos;s all, thanks for reading!</p><hr><h2 id="check-out-my-sourcecode-for-the-client-and-server">Check out my sourcecode for the &gt;<a href="https://github.com/Athrunen/MIDIShortCuts">client</a>&lt; and &gt;<a href="https://github.com/athrunen/ThatLEDController">server</a>&lt;</h2>]]></content:encoded></item><item><title><![CDATA[Building a controller for self-made daylight lamps or softboxes]]></title><description><![CDATA[Going from an old monitor backlight to colorful delight in no time.]]></description><link>https://blog.athrunen.dev/recycling-old-display-backlight-as-daylight-lamps-or-softboxes/</link><guid isPermaLink="false">5dda0fa54b7dde000198ac3d</guid><category><![CDATA[Embedded programming]]></category><dc:creator><![CDATA[Malte Vrampe]]></dc:creator><pubDate>Sun, 12 Jan 2020 21:33:00 GMT</pubDate><media:content url="https://blog.athrunen.dev/content/images/2020/01/B69zEhWZA8UBQgZtAtSEGdYDPAK6X9qFSyStZMLVGRAHqtB3YgTydKsfa9jr9ADhu9z5v9yYE8ERgZhgVti3MvTjReojAw5oBs1NURgi.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.athrunen.dev/content/images/2020/01/B69zEhWZA8UBQgZtAtSEGdYDPAK6X9qFSyStZMLVGRAHqtB3YgTydKsfa9jr9ADhu9z5v9yYE8ERgZhgVti3MvTjReojAw5oBs1NURgi.jpg" alt="Building a controller for self-made daylight lamps or softboxes"><p>I got the original idea for this project after watching a video by DIY Perks.<br><br>He shows how to extract the backlights of old monitors as well as TVs and explains the effects of the different layers build into them and how they make for a better light source:</p><figure class="kg-card kg-embed-card"><iframe width="480" height="270" src="https://www.youtube.com/embed/8JrqH2oOTK4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></figure><p><br><br>I will start after the actual extraction process and explain how I created my own RGBW controller with an ESP32, some buttons, and a small display.</p><p>As I&apos;m writing parts of this in retrospect I won&apos;t be able to show every step but rather the respective parts.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2020/01/IMG_20191225_211812_HDR-1.jpg" class="kg-image" alt="Building a controller for self-made daylight lamps or softboxes" loading="lazy"><figcaption>Sneak peek of my crude prototype</figcaption></figure><hr><h2 id="1-preparing-the-backlight">1. Preparing the backlight</h2><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2019/12/IMG_20191225_220724_HDR-1-.jpg" class="kg-image" alt="Building a controller for self-made daylight lamps or softboxes" loading="lazy"><figcaption>One of the removed lamps</figcaption></figure><p>The only thing I did after fully disassembling the backlight was to replace the build-in fluorescent lamps with an RGBW strip.</p><p>The strip was split into two and connected with some wires I had lying around. After that, I added some milky plastic in front of the strip for some additional diffusion and assembled the backlight again. </p><hr><h2 id="2-designing-the-controller">2. Designing the controller</h2><h3 id="2-1-the-microcontroller">2.1 The microcontroller</h3><p>For the project, I wanted to prototype fast and without being too concerned about the limitations of the hardware.</p><p>That&apos;s why the ESP32 was such an easy choice, as it:</p><ul><li>is quite small</li><li>has moderate ram and flash storage</li><li>has integrated WiFi and Bluetooth</li><li>is something I have experience with</li><li>is fast enough for more complicated computations(animations or different color selections for example)</li></ul><hr><h3 id="2-2-choosing-an-ide">2.2 Choosing an IDE</h3><p>For a development platform, I deliberately chose against using ArduinoIDE even though I&apos;m using its framework and am experienced with the IDE.</p><p>What I did chose is the VSCode integration PlatformIO, which allows to easily build projects for Arduinos, ESPs, and the likes. And as it is integrated into VSC it can leverage all the other integrations and tools build into that.</p><p>In addition to that is PlatformIO more friendly for bigger and more structured projects and encourages them, as it&apos;s:</p><ul><li>creating a local lib folder for project-specific libraries</li><li>creating a test folder to be used by the PIO unit test framework</li><li>having project configurations in an extra file allowing for multiple devices and environments in the same project</li></ul><hr><h3 id="2-3-deciding-on-a-framework">2.3 Deciding on a framework</h3><p>The last part of the puzzle was deciding on the actual framework to use.<br>Like I said above I chose Arduino, but let&apos;s see what frameworks I looked at and why I decided that way. &#xA0;</p><p>I had to decide between the standard Arduino framework that works with almost every microcontroller under the sun and the ESP specific framework ESP-IDF provided by the manufacturer.</p><p>The standard Arduino framework has far more libraries than the ESP one, taking away lots of the work needed. ESP-IDF, on the other hand, allows to fully take advantage of all features the ESP provides, without any workarounds and loss of performance.</p><p>And while fully utilizing the capabilities of the ESP32 sounded like fun, I wanted to leverage as much of the already existing tools already written for Arduino, for example, the display library I&apos;m using, without having to port everything to work with ESP-IDF.</p><p>In addition to that, I already had a lot of experience working with the Arduino framework.</p><hr><h2 id="3-designing-the-board">3. Designing the board</h2><h3 id="3-1-materials">3.1 Materials</h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2020/01/IMG_20200103_162404_DRO.jpg" class="kg-image" alt="Building a controller for self-made daylight lamps or softboxes" loading="lazy"><figcaption>The devboard I am using</figcaption></figure><p>As I did not want to build the additional periphery needed to work with a bare-bone ESP32 I opted for a devboard variant that already included those things and does not need to be surface mounted. And to save myself some headaches I also got some female headers as well to be able to swap out the board should I accidentally kill it, like I inadvertently would(already did.. twice..).</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2020/01/IMG_20200106_095328_DRO_crop.jpg" class="kg-image" alt="Building a controller for self-made daylight lamps or softboxes" loading="lazy"><figcaption>The display and buttons</figcaption></figure><p>While I originally wanted the controller to be primarily used wirelessly, I soon realized that it would be much better to also have some way to directly interact with the device. <br>That would allow the device to be much simpler and much more immediate to control as well as allowing me to focus on the basic features of an RGBW controller without having to implement any WiFi or Bluetooth support from the get-go.<br>And that&apos;s why I added a small OLED display and three buttons.</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2020/01/IMG_20200106_095408_HDR_crop.jpg" class="kg-image" alt="Building a controller for self-made daylight lamps or softboxes" loading="lazy"><figcaption>The voltage regulator in the middle, its capacitors in the lower right and the 12V jack connected to the left</figcaption></figure><p>Not wanting to power the controller with two different voltages I added an L7805 voltage regulator as well. The devboard I used directly supports 5V so there was no need to get a regulator that outputs 3.3V. As with most voltage regulators, there is one capacitor added between the input and ground and one between output and ground to increase stability. In my case, they&apos;re both 100uF electrolytic ones.</p><p>Lastly, to actually control my RGBW strip running on 12V I opted for some IRFZ44N MOSFETs that would allow me to switch the 12V using the 3.3V of the ESP32.<br>Additionally, there will be a 1k resistor between the gate of the MOSFET and the pin of the ESP to dampen unwanted oscillation.</p><p>That means in terms of electronic components, minus cables and connectors, we got:</p><ul><li>1 x <a href="https://amzn.to/2sDcUws">ESP32 devboard</a> (amazon affiliate link)</li><li>1 x L7805 voltage regulator</li><li>2 x 100uF electrolytic capacitors</li><li>4 x IRFZ44N MOSFETs</li><li>4 x 1k resistors</li><li>1 x <a href="https://amzn.to/2tzibF5">OLED display</a> (amazon affiliate link)</li><li>3 x buttons</li><li>1 x <a href="https://amzn.to/2FjfU3D">RGBW strip</a> (amazon affiliate link)</li></ul><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2020/01/Schematic_ESP32RGBW_Schematic_20200102043939.svg" class="kg-image" alt="Building a controller for self-made daylight lamps or softboxes" loading="lazy"><figcaption>My schematic</figcaption></figure><p>To the lower left, we got the 12V power jack as well as the connector for the display, in the center at the top we got the voltage regulator that takes in the 12V and converts it to 5V for the microcontroller. <br>To the right, we got the three buttons at the top and the four MOSFETs at the bottom. <br>And last but not least: that giant thing in the upper left corner is the devboard, unsurprisingly.</p><hr><h3 id="3-2-the-perfboard-prototype">3.2 The perfboard prototype</h3><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2020/01/IMG_20191225_211708_HDR.jpg" width="2000" height="1126" loading="lazy" alt="Building a controller for self-made daylight lamps or softboxes" srcset="https://blog.athrunen.dev/content/images/size/w600/2020/01/IMG_20191225_211708_HDR.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2020/01/IMG_20191225_211708_HDR.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2020/01/IMG_20191225_211708_HDR.jpg 1600w, https://blog.athrunen.dev/content/images/size/w2400/2020/01/IMG_20191225_211708_HDR.jpg 2400w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2020/01/IMG_20191225_211835_HDR.jpg" width="2000" height="1125" loading="lazy" alt="Building a controller for self-made daylight lamps or softboxes" srcset="https://blog.athrunen.dev/content/images/size/w600/2020/01/IMG_20191225_211835_HDR.jpg 600w, https://blog.athrunen.dev/content/images/size/w1000/2020/01/IMG_20191225_211835_HDR.jpg 1000w, https://blog.athrunen.dev/content/images/size/w1600/2020/01/IMG_20191225_211835_HDR.jpg 1600w, https://blog.athrunen.dev/content/images/size/w2400/2020/01/IMG_20191225_211835_HDR.jpg 2400w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>Let&apos;s not look at this for too long..</figcaption></figure><p>This horrendous looking thing is the cobbled-together version of my initial idea of a simple RGBW controller to which I later added the display and buttons, as well as a cable for one of the internal touch sensors of the ESP that I later removed.</p><p>The stuff I fucked up:</p><ul><li>Wired the MOSFETs the wrong way and had to cheat to get them connected the right way</li><li>Did not use any of the internal resistors you can activate from code(pulling down the buttons in this case)</li><li>Tried to use an input-only pin for PWM output, had to rewire a fair bit</li><li>The display is directly connected to the headers from below with some thin wires and is therefore easy to rip off accidentally</li></ul><hr><h3 id="3-3-first-pcb-prototype">3.3 First PCB prototype</h3><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2020/01/Screenshot_1.png" width="1410" height="838" loading="lazy" alt="Building a controller for self-made daylight lamps or softboxes" srcset="https://blog.athrunen.dev/content/images/size/w600/2020/01/Screenshot_1.png 600w, https://blog.athrunen.dev/content/images/size/w1000/2020/01/Screenshot_1.png 1000w, https://blog.athrunen.dev/content/images/2020/01/Screenshot_1.png 1410w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2020/01/Screenshot_2.png" width="1409" height="838" loading="lazy" alt="Building a controller for self-made daylight lamps or softboxes" srcset="https://blog.athrunen.dev/content/images/size/w600/2020/01/Screenshot_2.png 600w, https://blog.athrunen.dev/content/images/size/w1000/2020/01/Screenshot_2.png 1000w, https://blog.athrunen.dev/content/images/2020/01/Screenshot_2.png 1409w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>Version 1.0</figcaption></figure><p>After finishing the prototype and the first iteration of the codebase I started to create an actual PCB design, trying to fix some of the issues mentioned above and get something better looking.</p><p>Yet I still made some mistakes, given my inexperience with designing PCBs or even designing circuits for that matter:</p><ul><li>Placing the MOSFETs in the lower right made them quite hard to route</li><li>Why is the SPI connector to the left of the power jack?</li><li>12V wires are to thin to support the RGBW strip at its max current draw</li><li>Lacking any kind of meaningful labels</li></ul><hr><h2 id="4-the-actual-coding">4. The actual coding</h2><h3 id="4-1-designing-the-interface">4.1 Designing the interface</h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2020/01/IMG_20200106_102048_HDR.jpg" class="kg-image" alt="Building a controller for self-made daylight lamps or softboxes" loading="lazy"><figcaption>Looks actually quite nice, doesn&apos;t it?</figcaption></figure><p>Designing the interface was actually quite a lot of fun. Having such a limited amount of pixels that you could, in fact, count them, but enough to not have to work that abstractly made it a manageable task.</p><p>The library I was using to control the display had some nice helper functions that made my life a lot easier. So I used my math skills to create a simple formula that could, given some amounts and spacing information, return the distance between two elements.</p><p>This allowed me to dynamically set the number of sections I wanted and place elements accordingly. And while my original idea to use circles was technically working but, let&apos;s say.. not that pleasant to the eye in terms of symmetry, replacing them with some rectangular bars that fill up accordingly from the bottom worked even better.</p><hr><h3 id="4-2-adding-hsv">4.2 Adding HSV</h3><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2020/01/HSV_cone.png" class="kg-image" alt="Building a controller for self-made daylight lamps or softboxes" loading="lazy"><figcaption>HSV color space as a cone (Source: (3ucky(3all [<a href="http://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA</a>])</figcaption></figure><p>After cleaning up my code somewhat and separating it into manageable chunks I started to add some more features.</p><p>To make it easier to select a specific color without having to know its RGB components and without needing to try too many times, I chose to add a second color model called <strong>HSV</strong> that represents a given color by its <strong>H</strong>ue, <strong>S</strong>aturation, and <strong>V</strong>alue.</p><p><strong>Hue</strong> is the actual color of the light.</p><p><strong>Saturation</strong> is the amount of white mixed into the light.</p><p><strong>Value</strong> adjusts the brightness of the light itself.</p><hr><h3 id="4-3-calculating-white-color-portion">4.3 Calculating white color portion</h3><p>To actually use the W in the RGBW strip we need to calculate how desaturated the color is and use that to set the amount of white. If you want some more in-depth explanation check out my other <a href="https://blog.athrunen.dev/experimenting-with-efficiently-combining-rgb-and-true-white/">post</a>.</p><p>But the simplest way to do this is just to get the smallest of the RGB values, set white to that and set the original value to zero. </p><p>You might have been able to see from the picture of the display above that I kept the ability to set a value for W. It now controls how much white of the RGB spectrum is converted to the white LED. 0(or 0.0) is no conversion and 255(or 1.0) is a full conversion.</p><hr><h3 id="4-4-adding-some-unit-tests">4.4 Adding some unit tests</h3><p>To catch some problems early on I followed <a href="https://blog.athrunen.dev/learning-hardware-programming-as-a-software-engineer">my own advice</a> and started adding some unit tests. They are currently only there to test that I didn&apos;t fuck up the color conversions but will be extended to other parts of the code in the future.</p><p>As I extracted the color code into its own library I can now test it natively without having a board to test it on lying around.</p><hr><h2 id="5-conclusion-and-a-look-into-the-future">5. Conclusion and a look into the future</h2><h3 id="5-1-conclusion">5.1 Conclusion</h3><p>The process of creating the controller as well as playing around with the LEDs was a lot of fun that I want to continue with in the future. </p><p><br>And after transforming my spaghetti code into more manageable chunks and writing some actual unit tests working with the code became a lot easier and makes it actually extendable for features that I want to add in the future. </p><p>I learned a fair bit about color regarding the RGB color space as well as color mixing in general and how to use my white LED. More about that <a href="https://blog.athrunen.dev/experimenting-with-efficiently-combining-rgb-and-true-white/">here</a>.<br>In addition to that, I started to get better at working with microcontrollers in general and how to handle them. More about that <a href="https://blog.athrunen.dev/learning-hardware-programming-as-a-software-engineer">here</a>.</p><p>All in all a very enjoyable process.. not that I thought that while working at the project.. that consisted of a lot more swearing.</p><p>But as I do not want to stop the project yet, here is a look into the future:</p><hr><h3 id="5-2-design-a-better-board">5.2 Design a better board</h3><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2020/01/Screenshot_3.png" width="1446" height="838" loading="lazy" alt="Building a controller for self-made daylight lamps or softboxes" srcset="https://blog.athrunen.dev/content/images/size/w600/2020/01/Screenshot_3.png 600w, https://blog.athrunen.dev/content/images/size/w1000/2020/01/Screenshot_3.png 1000w, https://blog.athrunen.dev/content/images/2020/01/Screenshot_3.png 1446w" sizes="(min-width: 720px) 720px"></div><div class="kg-gallery-image"><img src="https://blog.athrunen.dev/content/images/2020/01/Screenshot_4.png" width="1448" height="838" loading="lazy" alt="Building a controller for self-made daylight lamps or softboxes" srcset="https://blog.athrunen.dev/content/images/size/w600/2020/01/Screenshot_4.png 600w, https://blog.athrunen.dev/content/images/size/w1000/2020/01/Screenshot_4.png 1000w, https://blog.athrunen.dev/content/images/2020/01/Screenshot_4.png 1448w" sizes="(min-width: 720px) 720px"></div></div></div><figcaption>Version 2.0</figcaption></figure><p>Before I get some actual PCBs I want to clean a few things up and change some of the control elements. This is the second version of the design that also supports longer strips without melting the traces as well as some rotary encoders instead of the hard to control buttons. </p><p>This is the version of the board that I actually want to get professionally made. There might be one or two things that will change before that happens but it&apos;s the general idea. </p><p>Changes:</p><ul><li>Replaced buttons with two rotary encoders</li><li>Adjusted trace width for the 12V wires(2mm width, 0.6mm clearance)</li><li>Moved MOSFETs closer to the connector</li></ul><p>Criticism:</p><ul><li>Still missing labels for the connectors</li></ul><hr><h3 id="5-3-continue-working-on-the-controller-firmware">5.3 Continue working on the controller firmware</h3><p>I&apos;m working on a better way to control the connected display with some more &quot;high-level&quot; functions. In the future, I want to be able to add and remove different displays with ease and switch between them.</p><p>I also want to add support for RGB+CCT(that are strips with two different white LEDs) as well as strips with individually addressable LEDs. <br>That would mean that I have to add one more MOSFET for the extra white LED and expose some additional data pins for the data lines of the individually addressable LEDs.</p><p>To make the actual color experience even better I want to increase the resolution of the PWM used to dim the different colors from 8-bits to 16-bits.<br>That would allow for far smoother transitions as well as some leeway to add actual color correction without it looking choppy. If you want to learn more about that, check out <a href="https://blog.saikoled.com/post/45630908157/implementing-arbitrary-color-correction-with-the">this blog post by saikoled</a>. </p><hr><h2 id="check-out-my-sourcecode">Check out my &gt;<a href="https://github.com/athrunen/ThatLEDController">sourcecode</a>&lt;</h2>]]></content:encoded></item><item><title><![CDATA[Experimenting with RGBW color mixing]]></title><description><![CDATA[Understanding how colors are mixed, what the problems of RGB LEDs are and how to combat them using actual white leds]]></description><link>https://blog.athrunen.dev/experimenting-with-efficiently-combining-rgb-and-true-white/</link><guid isPermaLink="false">5df6914e16c0ab0001882e93</guid><category><![CDATA[Embedded programming]]></category><category><![CDATA[C++]]></category><dc:creator><![CDATA[Malte Vrampe]]></dc:creator><pubDate>Mon, 23 Dec 2019 04:50:00 GMT</pubDate><media:content url="https://blog.athrunen.dev/content/images/2022/06/ezgif-2-941c20deb4.webp" medium="image"/><content:encoded><![CDATA[<img src="https://blog.athrunen.dev/content/images/2022/06/ezgif-2-941c20deb4.webp" alt="Experimenting with RGBW color mixing"><p>For my latest project, I wanted to use RGBW LEDs instead of just RGB ones. <br>As they have some interesting advantages over RGB that I wanted to explore:</p><ul><li>A more accurate representation of white</li><li>A wider color spectrum</li></ul><p><em>This only works with somewhat desaturated colors. But at the end of the post I have some ideas that might help to work around that.</em></p><p>This would allow for better lighting of surfaces as well as more natural-looking colors. </p><p>Why that is and how we get there is what I will try to explain below:</p><hr><h2 id="what-is-color-exactly">What is color exactly?</h2><figure class="kg-card kg-image-card kg-width-wide"><img src="https://blog.athrunen.dev/content/images/2019/12/Light_dispersion_conceptual_waves.gif" class="kg-image" alt="Experimenting with RGBW color mixing" loading="lazy"></figure><h3 id="light-and-color">Light and color</h3><p>Light or visible light to be precise is a form of electromagnetic radiation with a wavelength of 400-700 nanometers. <strong>Electromagnetic radiation</strong> means that the wave got a magnetic as well as an electric field which both oscillate at the same time. Such a wave is emitted when an electron transitions from a higher to a lower state of energy, releasing the difference in the form of a photon. The length of one of the wave segments is used to identify the waves corresponding <strong>wavelength</strong>. </p><h3 id="what-the-wavelength-got-to-do-with-spectral-color">What the wavelength got to do with spectral color</h3><p>Relevant for us are the colors red, green and blue(RGB anyone?) that can be found at ~700 nm, ~530 nm, and ~470 nm respectively. At those wavelengths, they are considered (near-) <strong>spectral colors</strong>. That means that they are composed of only one wavelength or a relatively narrow band of wavelengths. Which means that they can be used to efficiently mix different colors.</p><hr><h2 id="mixing-colors">Mixing colors</h2><h3 id="additive-mixing">Additive mixing</h3><p>When different waves of light intersect and bundle up before they reach the eye, they can be perceived as a different color that appears lighter than the colors used to mix it. A light source will appear as white if enough colors are added up that way. <br><br>And as an example for mixing, if you get a red and a green light to combine, the resulting light will appear yellow.</p><h3 id="subtractive-mixing">Subtractive mixing</h3><p>This type of color mixing determines how an object looks in a specific light. The surface of an object is composed of one or many pigments. A pigment only reflects a specific wavelength and absorbs the rest. Mixing enough pigments will result in all light being absorbed, creating a black surface. <br><br>For example, a red surface illuminated by white light looks red because it absorbs all but the red light. But if you would use a blue light instead, the same surface would look black.</p><hr><h2 id="wavelength-spectra-of-rgb-and-white-leds">Wavelength spectra of RGB and white LEDs</h2><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2019/12/n1O5x.jpg" class="kg-image" alt="Experimenting with RGBW color mixing" loading="lazy"><figcaption>RGB LED Spectrum</figcaption></figure><p>Let&apos;s take a look at the emission graph above that shows what the possible spectrum of an RGB LED might look like. </p><p>Given a temperature of 25 &#xB0;C, the <strong>x-axis</strong> describes the wavelength and therefore the <strong>perceived color </strong>of the light and the <strong>y-axis</strong> describes the actual <strong>intensity</strong> of that wavelength. <br>And as you can see, their actual colors are intense but have a quite narrow range of emitted colors with some gaps in between. </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2019/12/qYtFm.jpg" class="kg-image" alt="Experimenting with RGBW color mixing" loading="lazy"><figcaption>White LED Spectrum</figcaption></figure><p>If we now look at the spectrum of a white LED, you can clearly see that there is a <strong>wider range</strong> of wavelengths.</p><p>To understand how this is archived we first need to understand what a <strong>Stokes shift</strong> is. <br>Quoting <a href="https://en.wikipedia.org/wiki/Stokes_shift">Wikipedia</a>: </p><blockquote>When a system (be it a <a href="https://en.wikipedia.org/wiki/Molecule">molecule</a> or <a href="https://en.wikipedia.org/wiki/Atom">atom</a>) absorbs a <a href="https://en.wikipedia.org/wiki/Photon">photon</a>, it gains energy and enters an excited state. One way for the system to relax is to emit a photon, thus losing its energy (another method would be the loss of energy as <a href="https://en.wikipedia.org/wiki/Heat">heat</a>). When the emitted photon has less energy than the absorbed photon, this energy difference is the Stokes shift.</blockquote><p>In the case of the white LED this means that some of the photons of the underlying blue LED are <strong>absorbed</strong> by the phosphor(or a similar substance) and are (re-)<strong>emitted</strong> with a longer wavelength, thereby <strong>broadening</strong> the spectrum.</p><hr><p>Now we can discern the problems of mixing RGB in the light(heh) of additive and subtractive mixing:<br></p><ul><li>Additive mixing works quite well, just needs some <strong>proper balancing</strong> to account for the &#xA0;capabilities of the different LEDs</li><li>Subtractive mixing produces <strong>wrong colors</strong> because of missing wavelengths between the three color</li></ul><p>But if we now add the white color into the mix we will be able to mitigate those problems at least for less saturated colors:</p><ul><li>By mixing on top of white we do not need to be concerned about having a perfect white to desaturate the color</li><li>Using a white LED gives us a wider and natural spectrum, making our colors illuminate surfaces more natural</li></ul><hr><h2 id="mixing-in-the-white">Mixing in the white<br></h2><p>To grasp how to use the white while mixing you will have to understand that in RGB <strong>only two colors are actually needed to mix a pure hue</strong>, while the third color is used solely to control the saturation. Take a look at the following graphic:</p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2019/12/543px-CIExy1931_AdobeRGB.png" class="kg-image" alt="Experimenting with RGBW color mixing" loading="lazy"><figcaption>Adobe RGB color space - <a href="https://commons.wikimedia.org/wiki/File:CIExy1931_AdobeRGB.png">Source</a></figcaption></figure><p>If we have a lot of red and blue and we add a little bit of green the resulting color is only shifted towards the <strong>white point</strong> D65.</p><p>That means we can easily <strong>swap</strong> the color with the lowest value with white and get almost the same result. Let&apos;s take a look at some code for that:</p><pre><code class="language-c++">// red = 200
// green = 150
// blue = 250
std::array&lt;int, 3&gt; rgb = {200, 150, 250};

// checking for the index of the smallest of the three values
// green is the smallest
// becomes index = 1
int index = std::min_element(color.begin(), color.end()) - color.begin();

// storing value for later
int value = rgb[index];

// setting green to 0
rgb[index] = 0;

// creating the RGBW output
// becomes rgbw = {200, 0, 250, 150}
str::array&lt;int, 4&gt; rgbw = {rgb[0], rgb[1], rgb[2], value};</code></pre><p>As already explained in the code the green would become zero and the white 150, hopefully resulting in a slightly desaturated purple.</p><hr><h2 id="adjusting-the-ratio">Adjusting the ratio</h2><p><br>To compensate for the <strong>difference in luminosity</strong> between the colored LEDs and the white LED I don&apos;t just replace the color with white but allow a specific factor to be used. <br>In my case, the factor is adjustable at runtime using a simple interface and some buttons.</p><p>In actual code that could look something like this:</p><pre><code class="language-cpp">// some arbitrary factor between 0 and 1
float factor = 0.6;

std::array&lt;int, 3&gt; rgb = {200, 150, 250};
int index = std::min_element(color.begin(), color.end()) - color.begin();

// getting 60% of green
int value = rgb[index] * factor;

// setting green to 40% of itself
rgb[index] = rgb[index] * (1 - factor);

// creating the RGBW output
// becomes rgbw = {200, 60, 250, 90}
str::array&lt;int, 4&gt; rgbw = {rgb[0], rgb[1], rgb[2], value};</code></pre><p>In this case, the white to green ratio(or <strong>pure white to barely white</strong> if you want) is 60% to 40% or 90:60 to use the actual numbers.</p><hr><h2 id="conclusion">Conclusion</h2><p></p><p>Using the white LED when mixing desaturated colors is fairly easy and can be achieved by just simply <strong>replacing all or some of the amount of white created by the third color with actual white</strong>. </p><p>The more desaturated the color is, the more are we able to reap the benefits of the wider color spectrum. The problem is, that on full saturations we have no white at all. <br>But I can think of at least two ways to increase the quality even in that case:</p><ul><li>Using more than three <strong>spectral colors</strong>(tetra-, pentachromatic LEDs)</li><li>Always adding some tiny amount of white(and maybe <strong>increase the resolution</strong> from 8-bit(256 steps) to 16-bit(65536 steps))</li></ul><p>And as I am already working on implementing the aforementioned 16-bit RGB resolution I might be able to try the last one out for myself.</p>]]></content:encoded></item><item><title><![CDATA[Learning embedded programming as a software engineer]]></title><description><![CDATA[How to respect your hardware, understand pinouts, embrace your limited flexibility and prevent dumb mistakes.]]></description><link>https://blog.athrunen.dev/learning-hardware-programming-as-a-software-engineer/</link><guid isPermaLink="false">5db55e6c453f1d000198ee8d</guid><category><![CDATA[Embedded programming]]></category><category><![CDATA[Opinion]]></category><dc:creator><![CDATA[Malte Vrampe]]></dc:creator><pubDate>Thu, 28 Nov 2019 13:35:31 GMT</pubDate><media:content url="https://blog.athrunen.dev/content/images/2020/01/photo-1555543451-eeaff357e0f3.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://blog.athrunen.dev/content/images/2020/01/photo-1555543451-eeaff357e0f3.jpg" alt="Learning embedded programming as a software engineer"><p>I&#x2019;ve had never really come into contact with embedded programming, working mostly in python or C#, until a friend of mine asked me for some help with programming a simple controller for RGB strips using Arduino Nanos.</p><p>We&apos;d, of course, fail spectacularly.</p><p>Not only did our hardware not work quite like intended and a few Nanos died in the process(but that&#x2019;s a story for another time), but I actually learned a lot from this and similar projects. </p><p>And I want to tell you some of my mistakes, what I learned by making them and how to prevent them.</p><hr><h2 id="understanding-pinouts">Understanding pinouts</h2><p></p><p>Let&#x2019;s start with something that is a little bit hard to read if you don&apos;t know what you are doing: <em>pinouts</em>.</p><figure class="kg-card kg-image-card kg-width-full kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2019/10/ESP32-Pinout-1.png" class="kg-image" alt="Learning embedded programming as a software engineer" loading="lazy"><figcaption>One of many esp32 pinouts</figcaption></figure><p>Let me show you how to use them by taking a look at the pinout above and help you to understand some of the more interesting types of pins there are and from which ones you should stay away.</p><hr><h3 id="some-additional-stuff-to-look-out-for">Some additional stuff to look out for</h3><p></p><p>First of all, and this is especially true for the esp32 and its variants, there are a lot of similar pinouts for nearly the same board.&#x200C;&#x200C; Doing the extra step and verifying that you are using the right pinout is crucial.. unless you like the smell of burned electronics.<br><br>And while you&apos;re at it, try looking up the name of your actual board and research the pin definition of the platform/firmware you are using because it may contain some useful pieces of information about the pins included.&#x200C;&#x200C;<br><br>This is quite important as the pinout itself may not contain all the information needed to work with a specific board, for example the limitations some of the pins may have. <br>In my case, the pins I wanted to use were input only.. good luck trying to control a mosfet with those.</p><hr><h3 id="types-of-pins-and-how-to-use-them">Types of pins and how to use them</h3><p></p><p>Let&apos;s return to the topic at hand about what types of pins there are and when and how one should use them.</p><p>The most basic pins are <strong>digital pins</strong>, they can only either be on or off. You would, for example, use them to check if a button is pressed. Or if you use them as output, turning a led on or off.</p><p>But if you need finer control you have to use an <strong>analog pin</strong>. By utilizing a analog to digital converter(ADC) they are able to read analog signals. But their accuracy is somewhat limited by their resolution, the esp32, for example, is limited to 4096 different levels(12 bit).<br> <br>And while those pins cannot output a true analog signal, they can use a technique called PWM to approximate one by only switching the signal on for some time.<br>Analog pins are usually used to get accurate data from sensors(a hall sensor for example) or to dim leds(rgb anyone?).</p><p>Then there are <strong>communication pins </strong>that allow the device to communicate. There are several interfaces to choose from, for example UART for direct communication, I2C for on-board internal communication and SPI and 1-Wire for inter device communication, just to name a few.</p><p>Beware of pins that are used by the device itself or are used to control the device, they may exhibit unexpected behavior(send signals in regular intervals or in an event for example) and using them could fry the chip.<br>This are, for example, the <strong>control pins</strong> as well as some of the pins described in the interlude above. Some of them are not as bad as others like sometimes lighting up the internal led is better than frying the on-board flash memory.</p><p>In the case of the esp32 there are also two more types of pins that are interesting for us, for one there are the <strong>touch pins </strong>which, as the name suggests, allow the board to detect human touch. But there are also two DACs(Digital to Analog Converter) that are able to output a true analog signal, which means instead of generating 1.65V on a 3.3V pin by turning it off half of the time, the pin has actually 1.65V. </p><hr><h2 id="limited-flexibility">Limited flexibility</h2><figure class="kg-card kg-image-card kg-width-full kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2019/10/arduino-2713093_1920-1.jpg" class="kg-image" alt="Learning embedded programming as a software engineer" loading="lazy"><figcaption>A breadboard used for prototyping</figcaption></figure><p>While it is still easy to just switch out some part of the program with something new on the software side, its not always that easy for the hardware side. Not only do you have to potentially reassemble the whole device, but you also have to make sure it is really compatible and doesn&#x2019;t do any damage.</p><p>With the availability of tools like <a href="https://easyeda.com/" rel="noopener">easyeda</a> and the possibility to use actual breadboards to plan, explore and test such options beforehand this is only a real problem if there has been no actual planning.&#x200C;&#x200C;And use <a href="https://platformio.org/" rel="noopener">PIOs</a> build-in <a href="https://docs.platformio.org/en/latest/plus/unit-testing.html" rel="noopener">unit testing engine</a> if you can, it allows running tests on your local host machine as well as the connected devices.</p><p>Which in turn allows you to catch a lot of bugs beforehand.</p><figure class="kg-card kg-image-card kg-width-full kg-card-hascaption"><img src="https://blog.athrunen.dev/content/images/2019/10/pio.png" class="kg-image" alt="Learning embedded programming as a software engineer" loading="lazy"><figcaption>PlatformIO as a Visual Studio Code extension</figcaption></figure><p>On that note, if you are not using PIO currently, you really should, it&#x2019;s integration in Visual Studio Code and Atom makes it really easy to use. And you can extend it feely with VSC or Atom extensions.</p><p>Also, &#xA0;if you split your build into parts as well and connect them using cables/sockets/etc. you can swap them out in the future(stepper motor drivers are a great example of this).</p><hr><h2 id="consequences-of-mistakes">Consequences of mistakes</h2><p></p><p>Speaking of doing real damage, when was the last time your software project caused your PC to start burning?</p><p>Never, unless you were working in C and put some pointers in some really unfortunate places while simultaneously convincing your os you are really allowed to do this.</p><p>Now let us take a look at the hardware side of things. &#x200C;&#x200C;Using the wrong input for a pin? Using the wrong pin in general? &#x200C;&#x200C;Switching a relay at the wrong time? Not switching a relay at the right time?&#x200C;&#x200C;Forgot that a specific component needs to be slowly shut down?</p><p>Does not sound great, does it? I for my part killed loads of components over the years and I know that I will kill loads of them in the future.</p><p>And while there&apos;s not always a good solution to prevent this, only can use careful planning and the addition of fuses in front of expensive and/or important parts to reduce the risk drastically.&#x200C;&#x200C; And if you know that some parts are prone to breaking, give them some sockets and save yourself some soldering time.</p><hr><h2 id="conclusion-tl-dr">Conclusion / TL;DR</h2><p></p><p>There are a lot of things one can do wrong when approaching embedded programming from a pure software programming standpoint.</p><p>But here are the things that helped me not produce as much magic smoke as I used to:</p><ul><li>Read your specification</li><li>Read the right specification</li><li>Check connections thoroughly</li><li>Plan your stuff</li><li>Split your hardware into parts</li><li>Don&#x2019;t hardwire everything</li><li>Prototype and test on breadboard</li><li>Ever heard of fuses?</li></ul><hr><p>If you want to support me please use my Amazon affiliate link below:&#x200C;&#x200C;<a href="https://www.amazon.com/gp/search?ie=UTF8&amp;tag=athrunen-20&amp;linkCode=ur2&amp;linkId=9cc81f53af389e5facd35c6e1d924932&amp;camp=1789&amp;creative=9325&amp;index=pc-hardware&amp;keywords=esp32">Link</a></p><p>Photo by <a href="https://unsplash.com/@hbtography?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Harrison Broadbent</a> / <a href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></p>]]></content:encoded></item></channel></rss>