spliced my esp32c3 into the control line for this ws2812 string so I can now write whatever control program I want. Perfect. I'm using this crate for emitting ws2812 command pulses using the esp32's remote control signal processor which is a neat hack.
The plan is to string this up around the perimeter of my apartment so I want to use some kind of expression evaluation library so that I can write patterns mapped over n and t without recompiling. Not sure whether exprtk can cross-compile to riscv but I think there's a decent native rust based expression evaluator that I could use too.
Upon closer review I had misread the circuit diagram for this board (DFRobot ESP32-C3 Beetle) and it actually does pass the 5V in from the USB-in directly to a board pin so I can power the string right off the Vin pin if this is plugged into USB power. As a result I can pack it down super small very easily.
If you are a regular visitor of my blog, you might know that (with a few exceptions) I've been posting a new blog posts every two weeks. Unfortunately I skipped this regular update last week, because I've been a little bit sick. Nothing major, but I didn't want to contaminate my boxes of SMD components with germs. So after a few days of sleep it's time to get back to business and start with something small. Literally.
Due to my mandatory rest, I haven't had the time to work on something interesting to blog about. Of course this happens every once in a while, and so my usual solution is to just go through my recent Ali Express deliveries, to see if there is something I can either get up and running, or kill with some magic smoke.
Soon I found a suitable candidate: the ATTiny10. This tiny guy contains a 12Mhz 8-bit micro controller with 1024 bytes of flash memory and 32 bytes of ram. Of course a micro controller is a bit boring without any in- or output, so i wanted to combine it with an other recent delivery: A WS2812B addressable RGB led in a 2020 package. Together with some 0.2mm enameled wire this can be a pretty fascinating micro-project.
To make the soldering a bit doable, I put some kapton-tape on my workbench, sticky side up. This allows me to keep both component in place while I try to reach the limit of my soldering skills.
First, let's connect some wires to the ATTiny10. With a macro lens in front of my iPhone's camera, it looks like it's huge. Unfortunately it's still roughly the size of my soldering iron's tip.
Let's start the surgery. Please be quiet, and don't breath to much or else we need to start all over again.
After a few burned fingers, too much soldering flux and to my own surprise I managed to connect wires to all six pins.
And a few minutes later, I managed to solder the WS2812 as well. Just a few more soldering joints and the programming header will be connected as well. This should allow me to program the ATTiny10 using the USBasp programmer.
And here it is! The end result! An illuminated led! Great isn't it?! OK! See you next time!
Ok, ok. I'm going to be honest here. The reason the LED is illuminated is just because I managed to add some "noise" data on the data line when I touched the micro controller while it was powered on. So nothing more than a lucky shot.
Of course I still need to program the ATTiny. But here's the catch: It turns out the ATTiny isn't supported by PlatformIO. So I had to revert back to the dreadful Arduino IDE.
David Johnson-Davies wrote a nice post on how to program the ATTiny10 and even supplied the necessary files needed to do so using the Arduino IDE. But as you can see, in the screenshot above, I ran into an error.
Now, to be honest I don't know what causes this issue. It might be one of the following:
The USBasp I'm using is damaged or doesn't support the necessary TPI protocol. (Unlikely)
The ATTiny isn't connected correctly, maybe do to a bad soldering connection. (Still a bit unlikely)
I overheated and damaged the ATTiny when I soldered the connections. (Sounds a bit more likely)
I'm doing something wrong with the software. (Sounds likely)
I'm still not fully recovered and missing something completely obvious. (99% sure this is it)
Of course I could have spent a week trying to solve this. But that would mean yet an other quiet week on my blog. So, instead I just end this write-up with a big anti-climax.
If I'm able to solve this issue in the coming two weeks, I'll report back in my next blog. If you have any suggestions or pointers to a solution, leave them in the comments down below.
PS. Still looking for something awesome? Make sure to check this.
With the activity board controller finally in place and tested. It's time to throw together the actual firmware. Let's fire up VSCode!
VSCode has been my code editor of choice for quite a while now. Microsoft did a great job in developing a light weight but super powerful code editor. And with the advent of a PlatformIO VSCode extension, this makes for THE perfect Arduino IDE.
That being said, It's time to start working on the final firmware. Or actually: the final firmware for now. Because the Activity Board will probably be a project which will receive some (software) updates over time.
All the board's functionality will be seperated into a bunch of controllers. There is no particular reason why I called them controllers, It just sounds like I know what I'm doing. For now, the code consists of the following 5 controller classes:
InputController: Responsible for reading all the switch states by communicating with the MCP23017 over I2C.
SevenSegmentController: Controls the 7-segment display by communicating with the MAX7219 seven segment display.
NeopixelController: Controls all the WS2812B RGB-LEDs using the FastLED library.
LedController: Controls all the regular LEDs (incorporated in some of the buttons) using the Arduino GPIO pins.
CommunicationController: Sends JSON commands (like the buttons state updates) to a Raspberry Pi using the ArduinoJson library.
All of the controller classes have a setup() method which is called in the main.cpp setup routine, and most of the controllers have an update() method which is being called during the main run loop.
All of the update() methods are non blocking, to make sure the Activity Board stays responsive. Any necessary delays are implemented by using the elapsedMillis library. But every so often, I just simply count the update ticks to check if I need to do something.
if (tick++ % 100 == 0) { // do something every 100th cycle. }
Most of the controllers are pretty straight forward, and are just there as an easy to use wrapper for the respective libraries. The only controller that is a bit more exotic, is the InputController. To be honest, this controller gave me some headaches.
Don't interrupt me!
The MCP23017 I2C IO expander is capable of firing interrupts whenever one of the inputs changes. Because of this, I connected the two interrupt outputs of the MCP23017 to the Arduino interrupt pins (Pin 2 & 3). It turned out I only needed to connect one, since the MCP23017 can mirror the interrupt signal on both pins. Luckily this was just resulted in a redundant connection, and didn't caused any issues.
Unfortunately there was a bigger problem which I didn't forsee. While the MCP23017 is capable of triggering the Arduino's interrupt pin(s), I'm not able to read out the pin states in the interrupt service routines, since I2C uses interrupts itself, which aren't available in the interrupt service routines.
This means I can set a flag to request an update in the main loop, but I can never act on any input change in the service routine itself. Now for most of the inputs this is absolutely no problem, but for the rotary encoder I really need to check the state for both pin A and B. Now, if these two pins were both connected to the two different MCP23017 registers, I could have solved this with the two Arduino Interrupt pins. Or better yet. If I would have just connected the Rotary encoder directly to the Arduino's interrupt pins, it would have been even easier. But of course ... I didn't.
So after a lot of grumbling, I decided to give up on the interrupts for the rotary encoder (for now), and simply read out the MCP23017 data every run loop. I might mean the encoder wouldn't react as expected, but I could always make some hardware modifications later.
And with taking this easy route, reading the MCP23017 state was pretty straight forward, using Mizraith's fork of the Adafruit MCP23017 library:
// Initialize the library. Adafruit_MCP23017 mcp; // Configure the MCP23017. mcp.begin(); // Use default address 0. mcp.setGPIOABMode(0xFFFF); // All ports input. mcp.setGPIOABPullUp(0xFFFF); // All ports pull up. // Read out the 16 bits. unsigned int newState = mcp.readGPIOAB();
And then it turned out I spent way to much time in overthinking it. Since non of my other controllers is blocking the main run loop, fetching the current state up the buttons every loop is easily fast enough to handle any rotary encoder input. Once again, it turns out KISS is the best approach: Keep It Simple, Stupid!
And with that issue out the way, it was a matter of hooking up all the controllers in my main.cpp file. Whenever an input change, execute an action for that specific input.
This setup really enables me to easily add more actions to any of the buttons. Now and in the future.
And by sending any input change as a json object over the serial port, I can continue using the inputs in my future Raspberry Pi implementation.
For now, it just resulted in one awesome looking activity board with a lot of light effects!
Enjoy the show!
Now, if you are interested in all the fine detail of the code, you can check out the full source code on GitHub. Of course it's fully supplied with unit and integration tests (NOPE!). And it's fully and well documented (NOPE!). Check it it out in the ActivityBoardController repository!
Read all posts in this series:
Part 1: Enzo’s Control Room
Part 2: Building the Box
Part 3: Fire up the Lasers!
Part 4: Spray Away!
Part 5: Push the button!
Part 6: Assembling the panel
Part 7: The dial on the board goes round and round
Part 8: Take control!
Part 9: It’s all about the code!
Part 10: Bake me some Pi!
Part 11: The Final Touch
With all of my projects that include one or more buttons, I know one thing for sure. The buttons will be extensively tested by my 1,5 year old tinkerer Enzo. My Automatic curtains need to open and closed more than a few times a day, and my robot arm's test buttons are being pressed repeatedly even while it isn't powered on. I think the message is clear: he wants his own buttons!
Now I must admit, this project is inspired by the awesome, incomparable Mission Control Desk by Jeff High Smith. But since both my budget, space and time is a bit more restricted, I'm going for a more compact solution.
In our living room, we have a Ikea Kallax cabinet with 25 square compartments. My goal is to build a control box, the size of one of these compartments. This way, it has a nice dedicated place in our living room, and doesn't take up too much space.
The control box will be made out of plywood. The front, however, will be made of acrylic sheet. In addition to the holes for the controls, the acrylic will be engraved with his name and decorative linings. With the help of some NeoPixel LED's, I hope to get a cool illumination effect.
Of course the wood will eventually get a paint job, and the inner backplate will get some cool looking decorations as well.
Luckily, my preferred supplier was able to help me source all the necessary parts for this project. It really felt like I was shopping in Charlie's chocolate factory!
To get an idea of the space I have, I took a piece of paper of the approximate size of the acrylic front and layed out the parts I want to incorporate. And although this composition isn't set in stone, it really helps me to visualize the end result.
To give you an idea of the parts I want to incorporate, let's review them left to right, top to bottom:
In the top left you see the main toggle switches. These are the big boys! They have an integrated LED and are protected with a spring loaded cover. Enzo better knows what he's doing when he flips these switches!
Below the big boys, I'll add a rotary encoder. Unlimited rotation and a button press. Of course, we need to get some visual feedback, so I'll add a 12 pixel NeoPixel LED ring around it.
Next up are the simple flip switches. You know, for the casual stuff. In addition to these switches, I'll add 3 female cinch plugs. I'm not sure if I will make these functional, but adding them won't hurt anyone.
The top middle will be prominently occupied by an 8 digit 7 segment display. Besides the count down to the next nuclear launch, it might be able to countdown to Enzo's next birthday.
Of course, a real control center has it's own screen. I'm not sure what I will use to drive it. But this Raspberry Pi screen might be a perfect solution for some serious data representation!
We need some sliding action as well. And this slide pot will make sure Enzo will! Once again, the Neopixels will give the appropriate visual feedback.
The three big push buttons in the top right are tipped as the main contenders to become Enzo's favorites. By default they include a white led, but of course these will be replaced by NeoPixels. Allowing me to make them light up in every color possible.
And last but not least. A bunch of rocker switches allow Enzo to turn off the thrusters, enable the backup power supply and engage the flux capacitor.
Of course, building a serious control board needs a lot of well-thought-out planning. How will I drive and handle all the I/O. How will I incorporate the led lights around the acrylic. What will drive the display? And do I need NASA's approval? A lot of questions to be answered ...
And with one of these questions, you can help me! Because as said, the name of my son, Enzo, will be incorporated into the design. But for the fun of it I want this to be an applicable abbreviation. Something like: Electronic Native Zone Operator. If you have a better suggestion, leave a comment down below and reap that eternal fame!
Read all posts in this series:
Part 1: Enzo’s Control Room
Part 2: Building the Box
Part 3: Fire up the Lasers!
Part 4: Spray Away!
Part 5: Push the button!
Part 6: Assembling the panel
Part 7: The dial on the board goes round and round
Part 8: Take control!
Part 9: It’s all about the code!
Part 10: Bake me some Pi!
Part 11: The Final Touch
Replacing the LED of an Arcade button with a Neopixel (WS2812) gives awesome results. A perfect start for yet an other project! 😁 #arcade #button #neopixel #ws2812 #led #color #fastled #switch #color #hue #electronics #soldering #arduino #nano #aliexpress
The WS2812 5050 RGB LED Ring is a circular development board featuring individually addressable LEDs, making it ideal for decorative lighting, artistic projects, and electronic development. Each LED on the ring contains a WS2812 driver chip integrated directly into a 5050 RGB LED Ring package, allowing for precise control of color, brightness, and patterns using a single data line. This makes the LED ring easy to interface with popular microcontrollers such as Arduino, Raspberry Pi, or ESP32. The ring's compact and versatile design enables seamless integration into projects like wearables, ambient lighting, gaming setups, and DIY decorative displays. The WS2812 LEDs are capable of producing vibrant, high-quality colors with 24-bit (8 bits per channel) resolution, offering smooth transitions and animations. The LEDs operate on a 5V power supply and are daisy-chainable, meaning multiple rings can be connected to create intricate and larger lighting designs.
LED-Würfel: Zufallszahlen auf der Pixelmatrix darstellen
In meinem vorherigen Beitrag 'Pixelmatrix Combo von Oxocard: So programmierst du die 5×5 LED Matrix' habe ich bereits die Grundlagen und Möglichkeiten der Pixelmatrix vorgestellt. In diesem Beitrag möchte ich nun einen Schritt weiter gehen und zeigen, wie du mit der 5 × 5 LED Matrix einen digitalen Würfel programmierst, der Zufallszahlen anzeigt. Dieses Projekt eignet sich hervorragend, um die Programmierung von Zufallszahlen und die Ansteuerung der LEDs in einem kreativen Projekt zu kombinieren.
https://youtu.be/RADOEflnNDs
Hinweis: Dieses Produkt wurde mir vom Entwickler der Oxocard auf der Maker Faire 2024 in Hannover kostenlos zur Verfügung gestellt. Meine Meinung und Bewertung des Produkts basieren jedoch ausschließlich auf meinen eigenen Erfahrungen und sind unabhängig von dieser Bereitstellung.
Bezug der Pixelmatrix von Oxocard
Die in diesem Beitrag verwendete Pixelmatrix bekommst du für derzeit 49 CHF (ca. 52 €) unter https://oxocard.ch/. Zu dem Set gehört neben der Pixelmatrix noch der Mikrocontroller Oxocard Connect, welchen du für diese Cartridge benötigst.
Rechts ist die Pixelmatrix Cartridge, da ich den Mikrocontroller bereits besitze, benötigte ich nicht das komplett Set.
Pixelmatrix von Oxocard
Programmieren der Oxocard Connect in NanoPy
Den Mikrocontroller kannst du in NanoPy via Python programmieren. Die Entwicklungsumgebung NanoPy ist sehr leistungsstark und bietet für erfahrene Entwickler sehr viele nützliche Features, welches man in anderen Editoren für Mikrocontroller schmerzhaft vermisst, zum Beispiel ein Debugger.
Editor - NanoPy für die Oxocard Connect
Programmieren eines Würfels mit der Pixelmatrix & der Oxocard Connect
Bevor wir mit der eigentlichen Programmierung starten, schauen wir uns an, wie man die Pixelmatrix programmiert. Jeder NeoPixel hat einen Index, welcher oben rechts beginnt und unten links endet.
Anordnung der NeoPixel auf der Pixelmatrix Combo von Oxocard
Mit der Funktion setDigitalLed können wir dann über diesen Index die LED ansteuern und einen RGB Wert übergeben.
#aktivieren der LED mit Index 1 und setzen der Farbe blau
setDigitalLed(1, 0, 0, 100)
Theoretisch können wir jeden Farbwert verwenden, hier empfehle ich dir die Seite https://htmlcolorcodes.com/, auf welcher du recht einfach einen solchen Wert generieren kannst.
Bitmaskieren der Zeilen
Da wir die sechs möglichen Werte des Würfels auf der Pixelmatrix anzeigen wollen, gibt es hier mehrere Lösungen. Der wohl einfachste Weg ist ein Array mit 25 Werten für jeden Wert, das ergibt dann 6 Arrays welche wir je nach ermittelter Zufallszahl verwenden. Theoretisch spricht nichts gegen diese Lösung, denn es ist sehr unwahrscheinlich, dass ein neuer Wert hinzukommt, jedoch ist diese nicht optimal.
Wir können auch jede Zeile in ein Bitmuster maskieren und so durch eine 0 und 1 den Zustand jedes NeoPixel abbilden.
Im nachfolgenden Beispiel stelle ich die Augenzahl 1 dar, es sind alle NeoPixel deaktiviert außer das Mittlere in der dritten Zeile.
digits =
Wir benötigen zusätzlich noch eine Funktion, welche uns den Status des Neopixels liefert. Ich definiere hier, dass ein Bitmuster eine Zeile (in Englisch row) ist und als zusätzlichen Parameter übergebe ich den Index pro Zeile. Als Rückgabewert erhalte ich ein true, wenn der NeoPixel aktiviert werden soll, andernfalls ein false.
def checkLed(row, index)->bool:
return row & (1
bool:
return row & (1
bool:
return row & (1
Read the full article