👁️ First glimpse on the ESP32-S3 Qualia with our new round displays! Perfect for huge eyes. Progress with porting the M4_Eyes code to Qualia ESP32-S3. Check the first blinks here 🔗 . For quick development, we're on platformio but it's Arduino-friendly. Some tweaks needed for 480x480 display but we're getting there. Every blink counts! 🖥️🔧📈
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality
Anya is LIVE right now
FREE
Free to watch • No registration required • HD streaming
Finished the #freeformfriday soldering for today. Although it is not complete ( a stand is missing) I am happy that it’s working. #diy #diyelectronics #electronics #circuitboard #electric #electricalengineering #electronic #electronics #arduino #arduinoproject #esp8266 #embedded #embeddedsystems #iot #internetofeverything #internetofthings #platformio #d1mini #freeformfriday #Repost @f.blumenberg with @make_repost (at Busan, South Korea) https://www.instagram.com/p/BvEvFxqHOnM/?utm_source=ig_tumblr_share&igshid=1mbir6g768eat
The Train Automator: Part 3 - Writing the Software
After finishing the electronics it is time to work on the software. Since the train automator runs on an Arduino Nano, it's time to fire up Visual Studio Code and start typing some C++.
I've mentioned it a few times before: Platform.io is the perfect integrated development environment if you want to work with microcontrollers. The "new" VSCode extension makes it even better, since VSCode is my code editor of choice.
The setup for the embedded software is based around some Manager classes and Controller classes. If you are a regular reader of my blog, you know I used those term only to make my code look more professional. Why? because I have no idea what i'm doing.
Anyway, here's the beakdown of the software:
DisplayController: Responsible for displaying all the nessecery information on the screen. This class creates the user interface.
RandomTimeTask: A class responsible for executing tasks at random time intervals.
SettingsManager: Responsible for the setting settings interface, as well as storing and retrieveing the various settings stored in the EEPROM.
SoundPlayer: Responsible for playing the sound clips via de DFPlayer Mini (MP3 player).
TrainController: Handeling the communication with the train (via the Roco MultiMAUS system).
main.ino: The glue between all the classes mentioned above.
The interface allows you to toggle the three random timers (Auto drive, Train Sound, Station Sound) using the on board buttons. The display will show the countdown towards the the moment the action will be initiated.
The randomly picked delays are within a preconfigured range. By using the random intervals, my dad's train module will get a more natural feel. Because lets face it: when does a train ever drive on the exact schedule!?
The randomly scheduled tasks (RandomTimeTask) are based on the Task library by Makuna. The possibility to build your own Task subclass was a perfect fit for this project.
Aside from the default timer interface, you can enter the setting screen by holding one of the three buttons. This will allow you to configures things like Train ID, Train Speed, Volume, Auto Drive Minimum Delay, Auto Drive Maximum Delay, Train Sound Minimum Delay, Train Sound Maximum Delay, Station Sound Minimum Delay and Station Sound Maximum Delay.
All these settings are stored in the Arduino's EEPROM, so they are non volatile (meaning they remain stored even when the Arduino is powered down).
Building these settings screens was extremely easy using the u8g2 graphics library. It already included all the nessecery input tools and is extremely powerful if you want to built user interfaces on small displays like the used 0.96" OLED.
Here's a small impression of the interface I built for this project:
Of course I couldn't have built this project without the hard work of Philip Gahtow who wrote the awesome XpressNet library. So a big thanks to Philip!
And with both the electronics as well as the software finished, it's time to incorporate the Digital Train Controller into my dad's train module. Stay tuned! :)
PS. Interested in the full sourcecode and Platform IO project? Check out the GitHub Repository here. Keep in mind that it comes without ANY form of support. :)
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality
Anya is LIVE right now
FREE
Free to watch • No registration required • HD streaming
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
Trying to debug some minor issues in my ActivityBoard project. The Rigol really is a big help! A great way to check the interrupts I’m trying to handle.
Now that the hardware part of my Electrocard is done, it's time to start working on the software side of my electronic business card.
This is a 3 part story. Check out the previous parts here:
Part 1: Designing the PCB.
Part 2: Soldering the board.
First of all, it's good to make a list of the desired functionalities. Of course, the OLED screen combined with the 3 push buttons give me a lot of nice opportunities, so most of all, the display will be used to display the important company information for as far as it isn't already printed on the PCB's.
Secondary, it would be nice to display some debug information regarding the battery, the memory and the software version.
And last but not least, it's mandatory to add a nice easter egg to a business card like this. But more about that later.
The controls
Since I have three buttons, and have both primary and secondary functionalities, I want to add a short press and long press feature to the buttons. This is easily done with the help of the clickButton library. It gives me a simple way to add 6 desired modes:
Button A, Short press: Power on / Display the startup screen.
Button B, Short press: Display my company's address.
Button C, Short press: Display my company's & blog's url.
Button A, Long press: Standby mode (also initiated after 30 seconds of inactivity).
Button B, Long press: Debug mode.
Button C, Long press: Start the easter egg.
With the help of the earlier mentioned button library and a simple state machine the user can switch between the various modes, without the need of an complicated user interface.
Controlling the display
Controlling the display isn't particular difficult, especially since there are a lot of example codes and libraries available to control the SSD1306 OLED controlled I've used. I've tried a few of them, and most of them run fine on the ATTiny85 processor. But during the process I found out there are actually two separate challenges.
Control the display by sending an image to the OLED controller.
Drawing realtime graphics onto the screen.
The first challenge isn't extremely difficult. Most of the available libraries allow you to send a 128x32 pixel monochrome image (a BPM converted to a HEX values stored in the micro controller's flash memory) to the OLED controller. It took a while to figure out how to convert the BPM to the correct values, since it depends a bit on how the library reads and writes the data. But after some googling I found a working combination of the TinyOLED library and the LCD Assistant image converter.
This is the same method as used for writing text to the OLED. In stead of one complete stream of image data, every letter has a small portion of data (6 bytes) which is read from the micro controller's flash memory.
The second part is a bit more complicated, and that is mostly due to the limited available memory (RAM) of the ATTiny85 micro controller.
The OLED expects a sequential stream of bytes to control 8 vertical pixels per byte. The 128x32 pixel oled is built up by four rows (called pages) of 8 vertical pixels (1 byte). Each 128 bytes width. In total that adds up to 512 bytes.
If you want to draw lines and shapes on any position on the screen, you'll need to create a buffer for those 4096 pixels (512 bytes) in the micro controller's memory, to which you can draw. After drawing to the buffer, you send the full buffer to the OLED and your lines and shapes are displayed on the screen. That's how most OLED libraries work (for example, the popular Adafruit SSD1306 library).
The problem is, that you'll need to allocate 512 bytes of memory in you micro controller as the buffer. Since the ATTiny85 I used only has 512 bytes of RAM in total, there isn't enough memory to allocate this buffer.
In other words: I can't allocate a buffer. I need to render the drawing while I'm writing all the graphics data to the OLED controller. This turned out to be a BIG challenge. And while I only needed this for incorporating the easter egg, it's something I wanted to solve.
The Easter Egg: Horizontal Micro Tetris!
So, as told, I wanted to add a nice easter egg. I already programmed Pong once. But with the 3 push switches and the width 128x32 pixel screen, i reckon Tetris was better suited. Or to be more precise: horizontal Tetris. The added benefit of tetris is that it doesn't need a high frame rate. And honestly, seeing such a cute little Tetris game on a business card, brings a smile to most people's faces.
Due to the size of the screen, I opted to go for Tetris blocks of 4x4 pixel squares. This way, 8 blocks will fit vertically, and there is room for 32 horizontal blocks. In total there will be room for 256 blocks.
The blocks that are already on screen will be stored in an array called the arenaMatrix. And since each vertical row has 8 blocks, this array will consist of 32 bytes.
unsigned char arenaMatrix [32] = {};
The currently falling piece will be stored in a 4x4 block matrix, called the userMatrix. And since since we only need 16 bits of data, we can easily store this in an integer.
unsigned int playerMatrix = 0;
In other words, for storing all the blocks, we only need 34 bytes of data. Leaving us 478 bytes for additional variables (like the score and the user's piece position) but of course also a lot of other objects and pieces of code that's loaded in to memory.
Since the two matrixes matrices contain every piece of information I want to display on screen during a game of tetris, the render function can "simply" lookup and calculate the bytes of data for the OLED controller on the fly. This solves the lack of the graphics buffer. Or, if you look at it from a different perspecitve: this is an alternative graphics buffer with a 1/16th resolution (since the blocks are made of of 4x4 pixels).
In any way: it allowed me to render tetris without the use of any helper methods like drawLine() or drawSquare(). It all comes down to flipping bits one by one. Which isn't the most convenient, but did teach me A LOT about bit manipulation.
Now, of course, drawing a few matrices to the screen isn't enough to make a playable tetris. It needs some timers to drop the pieces. It needs collision detection to check if the blocks hit an other block. It needs a way to check if a fill line is filled with blocks, and it needs a way to remove the fully filled lines.
The Code
I'd love to run you thru the code, but that would probably account for a full year of blog posts. So to make things easier, I put all my code on GitHub so you can take a look at everything that's in there. Feel free to take a look at the Electrocard Repository.
If you're not familiar with the PlatformIO folder structure, you might want to start at the entrance point of the firmware in the main.cpp file. If you're mainly curious for the Tetris code, check out the Tetris.cpp file.
Fun fact: the full code used almost every byte of flash available in the the ATTiny85 (8KB). Of course, I could work on making the code more efficient, but everything I wanted to be in there is in the code.
Game Over
And with this information, I think this project is a wrap. It was an extremely satisfying and fun project to work on. Both the electronics and the code.
Although I'm extremely satisfied with the end product, there are some things to reconsider if I start a similar product:
Consider a different micro controller. Finding a solution for the limited amount of memory of the ATTiny85 was fun, but having a little bit more memory would probably allow me to add a few nice effects.
If I did have an other micro controller, I'd probably have more GPIO pins, which allowed me to add more buttons, and a nice colorful LED. Every project is better with a LED.
In the future, I'll definitely check EVERY footprint before I order my PCB's.
Work on the software on the breadboard prototype before you finish up the PCB. This would have showed me the limiting factor of the ATTiny85. I would have given me a heads up before I pulled out all my hairs during the search for a solution.
If you have any question or suggestions about this project, feel free to leave them in the comments down below.