{"id":32616,"date":"2021-04-09T18:17:56","date_gmt":"2021-04-10T01:17:56","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=32616"},"modified":"2021-04-12T10:52:06","modified_gmt":"2021-04-12T17:52:06","slug":"show-dotnet-animating-40-leds-with-charlieplexing","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/show-dotnet-animating-40-leds-with-charlieplexing\/","title":{"rendered":"Show dotnet: Animating 40 LEDs with charlieplexing"},"content":{"rendered":"<p>For Pi day 2021, I published a fun post on how to <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/blinking-leds-with-raspberry-pi\/\">Blink LEDs with Raspberry Pi<\/a>. As part of that writeup, I wanted to include a 40 LED <a href=\"https:\/\/github.com\/dotnet\/iot\/tree\/main\/src\/devices\/Charlieplex\">charlieplexing<\/a> example, but couldn&#8217;t get it working. It is working now, and I decided to do a follow-up post to show it to you. What&#8217;s interesting is what I had to do make this sample work. Surprisingly, I only had to change <a href=\"https:\/\/github.com\/dotnet\/iot\/blob\/cecfa5466d89eaa47cb0c6652c50f7cf6a03b28b\/src\/devices\/Charlieplex\/CharlieplexSegment.cs#L197\">one line of code<\/a> in the <code>CharlieplexSegement<\/code> device binding. It&#8217;s this single line change that I want to share as much as being able to control the forty LEDs.<\/p>\n<blockquote><p>I&#8217;m using this post to start a new blog series I&#8217;m calling &#8220;show dotnet&#8221;, intended for Fridays. The new series is inspired by <a href=\"https:\/\/news.ycombinator.com\/show\">Show HN<\/a> over at Hacker News. The idea is to create space on the .NET blog for showing something interesting or new, with no requirements on being the perfect post or aligned with the current release. The post can be quite short or super long. Doesn&#8217;t matter. The main thing is that it demonstrates some interesting or useful scenario of using .NET. It&#8217;s also perfect for guest posts. I&#8217;ve already got the first few posts lined up, and will curate these posts for a while. If this works out, I&#8217;ll broaden who can post. For now, this blog series is an experiment.<\/p><\/blockquote>\n<h2>Animating 40 LEDs<\/h2>\n<p>Let&#8217;s take a look.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/04\/charlie-giphy.gif\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-32618\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/04\/charlie-giphy.gif\" alt=\"Image charlie giphy\" width=\"480\" height=\"270\" \/><\/a><\/p>\n<p>Make sure to click the image to see the animation.<\/p>\n<p>I&#8217;m using the same <a href=\"https:\/\/github.com\/dotnet\/iot\/blob\/main\/samples\/led-animate\/Program.cs\">demo code<\/a> as I did for the previous post. I have plans for writing some fancier code. Perhaps that will be another &#8220;show dotnet&#8221; post.<\/p>\n<p>In terms of <a href=\"https:\/\/github.com\/dotnet\/iot\/blob\/main\/samples\/led-animate\/Program.cs\">code<\/a>, my app looks like the following:<\/p>\n<pre><code class=\"csharp\">\/\/ To use with (8) directly connected GPIO pins\r\n\/\/ int[] pins = new int[] { 4, 17, 27, 22, 5, 6, 13, 19 };\r\n\/\/ using IOutputSegment segment = new GpioOutputSegment(pins);\r\n\r\n\/\/ To use with charlieplexing\r\nint[] pins = new int[] { 23, 24, 25, 12, 16, 20, 21};\r\nusing IOutputSegment segment = new CharlieplexSegment(pins, 40);\r\n\r\n\/\/ To use a shift register\r\n\/\/ using IOutputSegment segment = new ShiftRegister(ShiftRegisterPinMapping.Minimal, 8);\r\n<\/code><\/pre>\n<p>The rest of the code is identical to the rest of the <a href=\"https:\/\/github.com\/dotnet\/iot\/blob\/main\/samples\/led-animate\/Program.cs\">Program.cs file<\/a>.<\/p>\n<p>As you can see, I&#8217;m using 7 GPIO pins to control 40 LEDs. On the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Charlieplexing\">Charlieplexing wikipedia<\/a> page, you will see a table that demonstrates that 7 GPIO pins can be used to control a maximum of 42 LEDs. That means I&#8217;m using the 7 pins near to their limit with this example. I go into much <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/blinking-leds-with-raspberry-pi\/#charlieplexing\">more detail about charlieplexing in my last post on LEDs<\/a>.<\/p>\n<p>The following is a quick description of Charlieplexing taken from my <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/blinking-leds-with-raspberry-pi\/#charlieplexing\">earlier post<\/a>.<\/p>\n<p><em>It is a multiplexing scheme that does not require using an integrated circuit like a <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/blinking-leds-with-raspberry-pi\/#sn74hc595-shift-register\">shift register<\/a>. It takes advantage of the tri-state nature of GPIO pins, which can be either <code class=\" prettyprinted\"><span class=\"typ\">Low<\/span><\/code>,\u00a0<code class=\" prettyprinted\"><span class=\"typ\">High<\/span><\/code>\u00a0or\u00a0<code class=\" prettyprinted\"><span class=\"typ\">Input<\/span><\/code>.\u00a0<code class=\" prettyprinted\"><span class=\"typ\">Low<\/span><\/code>\u00a0acts as ground,\u00a0<code class=\" prettyprinted\"><span class=\"typ\">High<\/span><\/code>\u00a0is obviously power, and\u00a0<code class=\" prettyprinted\"><span class=\"typ\">Input<\/span><\/code>\u00a0doesn\u2019t allow power to flow in any direction. These three options enable you to create circuits on the fly, in software. It\u2019s a bit magic, definitely awesome, but also a bit of a hack.<\/em><\/p>\n<p>You might be thinking that charlieplexing has got to be the least practical way to control LEDs. Look at all those wires! It&#8217;s worse than an ethernet run in an office building. There is a lot of truth to that. It&#8217;s also time consuming to wire 40 LEDs. I made several mistakes and had to do it slowly. Now that I&#8217;ve done it a few times, I can go faster. I actually wrote a crude test tool to make it easier. If you want it, tell me in the comments, and I&#8217;ll make it available.<\/p>\n<p>Yes, there are a lot of wires. A lot of that is just a function of electronics on breadboards. If you want to control 40 LEDs, you will likely need 80 wires (one for each leg). Once you move to PCBs, things get a lot easier. For example, Adafruit has some nice <a href=\"https:\/\/www.adafruit.com\/product\/2965\">charlieplexed LED matrices<\/a>. They operate on the same principles, and have the advantage of hiding all of the required connections on the underside of the PCB. These charlieplexed LED matrices are on my list to target. I already have a few of them in my toy box.<\/p>\n<p>Perhaps I&#8217;ll demonstrate controlling 40 LEDs with shift registers in a follow up. The wiring required for that configuration would be very different, although there would still be a lot of wires.<\/p>\n<p>Let&#8217;s take another look at the same demo, 90 degrees rotated.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/04\/charlie-giphy-sideways.gif\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-32620\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/04\/charlie-giphy-sideways.gif\" alt=\"Image charlie giphy sideways\" width=\"480\" height=\"270\" \/><\/a><\/p>\n<p>I&#8217;m now going to transition to what I had to do to make this demo work.<\/p>\n<h2>Sleeping on the job<\/h2>\n<p>When I first wrote the <a href=\"https:\/\/github.com\/dotnet\/iot\/blob\/main\/src\/devices\/Charlieplex\/CharlieplexSegment.cs\">CharlieplexSegment<\/a> binding, I used <a href=\"https:\/\/github.com\/dotnet\/iot\/blob\/cecfa5466d89eaa47cb0c6652c50f7cf6a03b28b\/src\/devices\/Charlieplex\/CharlieplexSegment.cs#L197\">Thread.Sleep to create a delay<\/a>. You need delays to make <a href=\"https:\/\/en.wikipedia.org\/wiki\/Persistence_of_vision\">persistence of vision<\/a> systems work. I was aware at the time that <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.thread.sleep\">Thread.Sleep<\/a> wasn&#8217;t the right API because it is graduated in terms of integers representing milliseconds, making 1ms the minimum sleep. I knew from observation and looking at other examples that I needed a delay much shorter than 1ms.<\/p>\n<p>This situation is a little embarrassing. I should have been able to find an alternative myself, and I have excellent access to several threading and concurrency experts who would have been pleased to help me. Eventually, my colleagues at <a href=\"https:\/\/github.com\/dotnet\/iot\">dotnet\/iot<\/a> pointed me at <a href=\"https:\/\/github.com\/dotnet\/iot\/blob\/main\/src\/devices\/Common\/System\/Device\/DelayHelper.cs\">DelayHelpers.cs<\/a> for inspiration. <a href=\"https:\/\/github.com\/dotnet\/iot\/issues\/1249\">dotnet\/iot #1249<\/a> is also relevant.<\/p>\n<p>In the end, I just switched from <code>Thread.Sleep(1)<\/code> to <code>Thread.SpinWait(1)<\/code>. It&#8217;s like the old joke &#8220;I improved performance by removing all the calls to <code>Thread.Sleep<\/code>&#8220;. That&#8217;s what I did. As a result, the code is delayed ~1000x less. The impact of the change to my 40 LED example is both extremely obvious and pleasing.<\/p>\n<p>In short, <code>Thread.Sleep<\/code> uses operating system and CPU features that enable creating delays without affecting the overall performance of the machine by yielding, similar to <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.thread.yield\">Thread.Yield<\/a>. <code>Thread.Spin<\/code> is the opposite, it actually just uses the CPU &#8212; making a given core unavailable for any other execution &#8212; to create a short delay.<\/p>\n<p>In the process of writing this post, I asked my favorite threading experts to review this content. I was given the following advice (which I will absolutely apply).<\/p>\n<blockquote><p>Thread.SpinWait does not make guarantees about the wall-clock time that it is going to wait. That\u2019s not good \u2013 it makes the code fragile. For example, the next time you upgrade your RPi or we make a performance fix in the runtime, your code may break because it will be too fast again. Instead of just calling SpinWait, it would be better to active wait for a specific number of nanoseconds, like is done in <a href=\"https:\/\/github.com\/dotnet\/iot\/blob\/main\/src\/devices\/Common\/System\/Device\/DelayHelper.cs\">DelayHelpers<\/a><\/p><\/blockquote>\n<p>I was also given the following detailed feedback:<\/p>\n<ul>\n<li>Thread.Sleep() and the SpinWait struct can be used when waiting for a state change in a multithreaded environment (a lock being released, change to a memory location, etc.) in a way that allows other threads to do work during the wait.<\/li>\n<li>The SpinWait struct starts with a short delay (using Thread.SpinWait()) and increases it up to a limit, eventually mixing in Sleep() to allow other threads to run, can be useful when it\u2019s unclear how much delay would be necessary to see the state change (eg. releasing a lock).<\/li>\n<li>Thread.SpinWait() asks the processor to issue a delay. It typically has a minimum delay in the low-nanosecond range or a few clock cycles. That low of a delay is not guaranteed by the OS, as the OS may still schedule-out the thread and let other threads run on the logical processor, which can lead to millisecond-level delays occasionally, though it may not be an issue if there is not much multiprocessing happening. The process may also be given higher priority.<\/li>\n<\/ul>\n<p>That&#8217;s really awesome insight that I wish I&#8217;d had earlier. As you can tell, I don&#8217;t work on threading for .NET.<\/p>\n<p>The following is a little more information that I picked up from various docs.<\/p>\n<p>This is what the <a href=\"https:\/\/docs.microsoft.com\/dotnet\/api\/system.threading.thread.spinwait\">.NET docs<\/a> say:<\/p>\n<blockquote><p>The SpinWait method is useful for implementing locks. Classes in the .NET Framework, such as Monitor and ReaderWriterLock, use this method internally. SpinWait essentially puts the processor into a very tight loop, with the loop count specified by the iterations parameter. The duration of the wait therefore depends on the speed of the processor.<\/p>\n<p>Contrast this with the Sleep method. A thread that calls Sleep yields the rest of its current slice of processor time, even if the specified interval is zero. Specifying a non-zero interval for Sleep removes the thread from consideration by the thread scheduler until the time interval has elapsed.<\/p><\/blockquote>\n<p>This is from <a href=\"https:\/\/en.wikipedia.org\/wiki\/Busy_waiting\">wikipedia<\/a>:<\/p>\n<blockquote><p>In low-level programming, busy-waits may actually be desirable. It may not be desirable or practical to implement interrupt-driven processing for every hardware device, particularly those that are seldom accessed. Sometimes it is necessary to write some sort of control data to hardware and then fetch device status resulting from the write operation, status that may not become valid until a number of machine cycles have elapsed following the write. The programmer could call an operating system delay function, but doing so may consume more time than would be expended in spinning for a few clock cycles waiting for the device to return its status.<\/p><\/blockquote>\n<p>That very much describes my use case.<\/p>\n<h2>Running on Raspberry Pi 4 with Arm64<\/h2>\n<p>I have several Raspberry Pis on my desk. I have one Pi 2, and then several Pi 3 and Pi 4s. I often install the official <a href=\"https:\/\/www.raspberrypi.org\/software\/operating-systems\/#raspberry-pi-os-32-bit\">32-bit Raspberry Pi OS<\/a>. It works great out of the box. I always install the &#8220;Lite&#8221; version, since I exclusively ssh into my <a href=\"https:\/\/www.raspberrypi.org\/documentation\/remote-access\/\">headless Pis<\/a>. Using the 32-bit version doesn&#8217;t really make sense for me, since my team is focused so heavily on Arm64. I should be testing that.<\/p>\n<p>I recently switched to using the <a href=\"https:\/\/www.raspberrypi.org\/software\/\">Raspberry Pi Imager<\/a> after having using Etcher for many years. I decided to install a 64-bit OS offered in its operating system catalog. I chose <a href=\"https:\/\/manjaro.org\/\">Manjaro<\/a> since I was already familiar with it from using <a href=\"https:\/\/www.pine64.org\/pinebook-pro\/\">Pine64 products<\/a>. Manjaro is a great distro, in part because it is in the <a href=\"https:\/\/archlinux.org\/\">Arch<\/a> family, and Arch has an amazing <a href=\"https:\/\/wiki.archlinux.org\/\">wiki<\/a>. The <a href=\"https:\/\/wiki.manjaro.org\/\">Manjaro wiki<\/a> is also great. I&#8217;ve used both wikis a fair bit.<\/p>\n<p>You can see me selecting Manjaro in the image below. I choice the Minimal version. It&#8217;s analogous to Rasperry Pi OS &#8220;Lite&#8221;.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/04\/raspberry-pi-imager.png\" alt=\"raspberry pi imager\" \/><\/p>\n<p>Manjaro has a very different setup process than Raspberry Pi OS, at least for the Minimal edition. When you first SSH into Manjaro (with the root user; no p\/w), you will be presented with a setup wizard (yes, over SSH). The questions relate to keyboard type and timezone. The Wizard takes maybe five minutes to work through. It was a surprise to me, compared to Raspberry Pi OS, but not a problem.<\/p>\n<p>I did run into some challenges, but was able to resolve them pretty quickly. Sharing those is the purpose of this section.<\/p>\n<p>I first configured Manjaro to authorize my WSL2 instance for SSH.<\/p>\n<pre><code class=\"bash\">cat .ssh\/id_rsa.pub | ssh rich@192.168.1.226 \"cat &gt;&gt; .ssh\/authorized_keys\"\r\n<\/code><\/pre>\n<p>I used this command to copy my WSL2 SSH public key to my Raspberry Pi. I logged in to to the Pi first to create an <code>.ssh<\/code> directory. I was able to avoid using passwords after that. I also used VS Code Remote to edit some of the files on the Pi via SSH and without using a password.<\/p>\n<p>You will notice that I&#8217;m using an IP address to SSH into my Pi. I&#8217;d prefer to use the hostname but <a href=\"https:\/\/github.com\/microsoft\/WSL\/issues\/4518\">WSL2 doesn&#8217;t currently support IPv6<\/a>. Raspberry Pi OS continues to support IPv4 so doesn&#8217;t have this problem. You may notice this later in my explanation.<\/p>\n<p>I first <a href=\"https:\/\/github.com\/dotnet\/core\/blob\/main\/release-notes\/6.0\/linux-packages.md#arch-linux\">installed the required dependencies for .NET<\/a> using <a href=\"https:\/\/wiki.manjaro.org\/index.php?title=Pacman_Overview\">pacman<\/a>. After that, I downloaded .NET via curl using the Linux Arm64 download links at <a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\">.NET downloads<\/a>. I installed .NET 6.<\/p>\n<pre><code class=\"bash\">sudo pacman -Syu \r\n    glibc \r\n    gcc \r\n    krb5 \r\n    icu \r\n    openssl \r\n    libc++ \r\n    zlib\r\ncurl -o dotnet.tar.gz https:\/\/download.visualstudio.microsoft.com\/download\/pr\/90d8a5e0-ed8f-430c-a66c-d17a096024a9\/95d17428d5b0da3552c502eede9f7f05\/dotnet-sdk-6.0.100-preview.3.21202.5-linux-arm64.tar.gz\r\nmkdir dotnet\r\ntar -C dotnet -xf dotnet.tar.gz\r\n.\/dotnet\/dotnet --info\r\nexport DOTNET_ROOT=~\/dotnet &amp;&amp; export DOTNET_ROLL_FORWARD=LatestMajor &amp;&amp; export DOTNET_ROLL_FORWARD_TO_PRERELEASE=1\r\n<\/code><\/pre>\n<p>I also used various <code>DOTNET<\/code> environment variables. For example, I&#8217;m going to be running an app that targets .NET 5. The two <code>DOTNET_ROLL_FORWARD<\/code> environment variables enable me to run the .NET 5 app on .NET 6 without changing the app. The <code>DOTNET_ROOT<\/code> environment variable is required so that application executables can find the runtime. This is also required by the tool that I&#8217;m going to install and run next.<\/p>\n<p>I installed the <a href=\"https:\/\/www.nuget.org\/packages\/dotnet-runtimeinfo\/\">dotnet-runtimeinfo<\/a> to validate that everything was working.<\/p>\n<pre><code class=\"bash\">.\/dotnet\/dotnet tool install -g dotnet-runtimeinfo\r\nexport PATH=\"$PATH:\/home\/rich\/.dotnet\/tools\"\r\n.\/dotnet\/dotnet runtimeinfo\r\n<\/code><\/pre>\n<p>It produces the following result:<\/p>\n<pre><code class=\"bash\">**.NET information\r\nVersion: 6.0.0\r\nFrameworkDescription: .NET 6.0.0-preview.3.21201.4\r\nLibraries version: 6.0.0-preview.3.21201.4\r\nLibraries hash: 236cb21e3c1992c8cee6935ce67e2125ac4687e8\r\n\r\n**Environment information\r\nOSDescription: Linux 5.10.17-1-MANJARO-ARM #1 SMP PREEMPT Mon Feb 22 11:29:03 CST 2021\r\nOSVersion: Unix 5.10.17.1\r\nOSArchitecture: Arm64\r\nProcessorCount: 4\r\n<\/code><\/pre>\n<p>The next challenge was more surprising. I was expecting to using the <a href=\"https:\/\/www.nuget.org\/packages\/System.Device.Gpio\/\">GPIO APIs<\/a>.<\/p>\n<p>My app crashed with the following stack trace.<\/p>\n<pre><code class=\"bash\">Unhandled exception. System.IO.IOException: Error 13 initializing the Gpio driver.\r\n   at System.Device.Gpio.Drivers.RaspberryPi3LinuxDriver.Initialize() in \/home\/rich\/iot\/src\/System.Device.Gpio\/System\/Device\/Gpio\/Drivers\/RaspberryPi3LinuxDriver.cs:line 603\r\n   at System.Device.Gpio.Drivers.RaspberryPi3LinuxDriver.OpenPin(Int32 pinNumber) in \/home\/rich\/iot\/src\/System.Device.Gpio\/System\/Device\/Gpio\/Drivers\/RaspberryPi3LinuxDriver.cs:line 159\r\n   at System.Device.Gpio.Drivers.RaspberryPi3Driver.OpenPin(Int32 pinNumber) in \/home\/rich\/iot\/src\/System.Device.Gpio\/System\/Device\/Gpio\/Drivers\/RaspberryPi3Driver.cs:line 179\r\n   at System.Device.Gpio.GpioController.OpenPinCore(Int32 pinNumber) in \/home\/rich\/iot\/src\/System.Device.Gpio\/System\/Device\/Gpio\/GpioController.cs:line 104\r\n   at System.Device.Gpio.GpioController.OpenPin(Int32 pinNumber) in \/home\/rich\/iot\/src\/System.Device.Gpio\/System\/Device\/Gpio\/GpioController.cs:line 93\r\n   at System.Device.Gpio.GpioController.OpenPin(Int32 pinNumber, PinMode mode) in \/home\/rich\/iot\/src\/System.Device.Gpio\/System\/Device\/Gpio\/GpioController.cs:line 114\r\n   at Iot.Device.Multiplexing.CharlieplexSegment..ctor(Int32[] pins, Int32 nodeCount, GpioController gpioController, Boolean shouldDispose) in \/home\/rich\/iot\/src\/devices\/Charlieplex\/CharlieplexSegment.cs:line 53\r\n   at &lt;Program&gt;$.&lt;Main&gt;$(String[] args) in \/home\/rich\/iot\/samples\/led-animate\/Program.cs:line 16\r\nAborted (core dumped)\r\n<\/code><\/pre>\n<p>I quickly discovered that my <a href=\"https:\/\/wiki.archlinux.org\/index.php\/Udev#About_udev_rules\">udev rules<\/a> were incorrect, after reading a\u00a0<a href=\"https:\/\/forum.manjaro.org\/t\/howto-raspberry-pi-temperature-and-humidity-sensor-dht22-dht11-am2302\/34685\">Manjaro forum post<\/a> on the same topic.<\/p>\n<p>I then copied working udev rules from my one Pi running Raspberry Pi OS to the one running Manjaro. I used WSL2 as the intermediary between the two, although it could be done other ways.<\/p>\n<pre><code class=\"bash\">ssh pi@raspberrypineapple \"cat \/lib\/udev\/rules.d\/60-rpi.gpio-common.rules\" | ssh rich@192.168.1.226 \"cat &gt;&gt; 60-rpi.gpio-common.rules\"\r\n<\/code><\/pre>\n<p>I then logged into my Pi running Manjaro and copied the rules to the right location, did some more configuration and then rebooted the Pi.<\/p>\n<pre><code class=\"bash\">sudo mv 60-rpi.gpio-common.rules \/lib\/udev\/rules.d\/\r\nsudo groupadd dialout\r\nsudo usermod -aG dialout $USER\r\nsudo reboot\r\n<\/code><\/pre>\n<p>That&#8217;s it. Everything worked after that.<\/p>\n<h2>Closing<\/h2>\n<p>Let&#8217;s see. What did I show you?<\/p>\n<ul>\n<li>You can do fun and cool stuff with .NET.<\/li>\n<li>In certain scenarios, you need to use low-level APIs to achieve the right level of required control, but carefully.<\/li>\n<li>Linux environments sometimes need a bit more configuration to establish the desired execution environment, and then .NET apps are at home from that point on.<\/li>\n<\/ul>\n<p>I hope you enjoyed the post, and the idea of the &#8220;show dotnet&#8221; blog series. Take care.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Charlieplexing is a fun way to animate LEDs. I&#8217;ll show you how I&#8217;m controlling 40 LEDs with 7 GPIO pins.<\/p>\n","protected":false},"author":1312,"featured_media":32624,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,196,7234],"tags":[4],"class_list":["post-32616","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-dotnet-core","category-show-dotnet","tag-net"],"acf":[],"blog_post_summary":"<p>Charlieplexing is a fun way to animate LEDs. I&#8217;ll show you how I&#8217;m controlling 40 LEDs with 7 GPIO pins.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/32616","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/1312"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=32616"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/32616\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/32624"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=32616"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=32616"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=32616"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}