An open source framework/toolkit for microcontrollers. Currently ErsatzMoco only supports Arduino from scratch, but the structure should allow for easy ports to other ecosystems. It is released under GPL v3 license.
- Unified wrappers for a number of commonly available displays
- Simple but nice and effective GUI toolkit that can integrate icons loaded from SD card
- Stepper motor control with linear acceleration and deceleration (stepper motor driver board required)
- Support for RFM69 radio modules
- Stable, simple and human-readable (simplifies debugging) messaging protocol that works with all communication modules integrated in ErsatzMoco (currently Serial and RFM69). The protocol offers the configurable modes „send without acknowledge“ and „send with acknowledge“, but not „send exactly once“. This is intentionally because the latter mode is very hard to implement in a way that prevents system lock-ups reliably. Please see „ErsatzMoco communication protocol“ for further information.
- Framework design that is intended to constantly keep track of the status of each component integrated in your setup. Currently, this is not fully implemented yet. The goal is to offer status updates from every component at any time on request. That way it should be possible to create a full virtual representation of a project, for example as a reliable control surface.
ErsatzMoco provides software modules for easy integration of common actuators and sensors. The objective is to get projects up and running much more quickly by eliminating the need to reinvent the wheel again and again - without being forced to use a special commercial hardware ecosystem.
I wrote this toolkit for myself so I could just hook up some common components, compile the necessary classes and be ready for experiments and refinements. While it’s always possible to log debugging messages via Serial/USB, I felt that’s not enough in some cases: I want to display messages of all kinds on any display integrated into my setup. So one important part of this toolkit are classes for building a simple but nice GUI on common microcontroller displays. Another important part is a universal message system: I want to enable my boards to talk to each other via any communication channel available in the setup.
This project definitely is constant work in progress. During development it became obvious that running a project with just one or two components hooked to the microcontroller is quite simple using low-level libraries provided with these components - but it can be a daunting task to control a greater number of actuators/sensors simultaneously since some libraries sometimes interfere with others. ErsatzMoco helps a little by providing higher-level classes that may persist even when you exchange one component for another. Still it’s both vital and cumbersome to keep an eye on the low-level libraries used in your project. This is the reason ErsatzMoco does not control stepper motors using hardware interrupts but instead via software clocks. This is less exact for sure, but it helps to maintain setups more easily that do not require timing close to the processor’s clock frequency. That said it should always be possible to combine your custom code with some helpful components of ErsatzMoco if necessary.
Currently, ErsatzMoco is written in C++ and relies on the standard Arduino environment plus an STL port like ArduinoSTL by Mike Matera. I prefer C++ to plain C because I like to code object oriented. My intention is to port ErsatzMoco to MicroPython/CircuitPython as well. The code uses some C++ features that are potentially dangerous in a microcontroller environment: Some objects are deliberately created on the heap instead of the stack. This could open memory leaks if it’s not properly taken care of, but I had reasons for this setup - please see the remarks in the source code. Furthermore, you will need a controller with sufficient memory. Currently, the compiled code integrating all modules available and including the necessary low-level libraries takes up almost 30k bytes. Especially stack/variable space and heap will probably be too large for an Arduino UNO very quick, so be careful if you try to use the toolkit on a controller with limited resources. Still I don’t consider this a problem anymore - boards are constantly getting faster while providing more and more memory resources without extra costs. Currently, the code is tested on the Adafruit Feather M0 only, but it should also (and is actually intended to for my own purposes) run on boards like the Arduino Mega 2560 and other microcontrollers with comparable resources. How many resources you need depends on the number of modules you include, of course, so a limited setup may run flawlessly on older and smaller boards as well.
To make ErsatzMoco work, you need to include several libraries. Some of them like the nice and indispensable RFM69 library by lowpowerlab may show some compilation quirks in the Arduino environment for arbitrary reasons. This may occur anytime with any library and is not related to ErsatzMoco - please consult the web on how to deal with problems like that. Sometimes there are also pitfalls really hard to find: The RFM69 library for example showed some complex interrupt issues on SAMD processors that crashed the program (when using the SPI bus for other purposes simultaneously, like with a display). I cannot deal with quirks like that myself, but please report them anyway - after some time someone may be able to track down the culprits and solve the problem. Note: The RFM69 module (class RFM69_Module.cpp) switches the radio module to the European frequency of 868 MHz. Please adjust the settings in the header file (RFM69_Module.hpp) according to your board, your country and your needs.
The main „control center“ classes of ErsatzMoco are specialized subclasses of MocoUnit. A MocoUnit subclass basically represents the box/thing/contraption you are working with. It keeps track of all subsystems and actuators and controls the clock. So you always need one subclass of MocoUnit like „Remote“ or „Locomotive“. The MocoUnit subclasses provided with ErsatzMoco give you an idea of how the toolkit works. Please study them carefully - they show which object instances need to be stored in global variables, how callbacks are used and so on. You may want to create your own subclass of MocoUnit according to your project. To integrate this main class with the standard Arduino environment you need to create the object in an (apart from that almost empty) “.ino“ file that forwards the standard Arduino polling cycle to your MocoUnit. Please see the example classes in the main directory ending with an underscore like „ErsatzMoco_Remote.cp_“. If you want to use one of them directly, simply create a copy and rename its suffix to “.ino“. This will be the file you open in the Arduino IDE for compilation. Place the „src“ and the „include“ directories of ErsatzMoco into the same directory your “.ino“ file resides in. If your project integrates elements for user interaction, e.g. push buttons, you should add an instance of UserInterface as well. The UserInterface class keeps track of any physical input devices. Again, please inspect MocoUnit subclasses for more information and have a look at the class diagrams that show the classes available and their relations. Anything else is pretty much optional and depends on your project. If your device uses a display, you probably want to add a suitable subclass of Display. A communication module - subclass of ComModule - is most useful as well because it integrates the ErsatzMoco communication protocol with your project.
This is an open source project, so contributions and bug reports are welcome and will be taken care of within the bounds of possibility. Nonetheless I currently want to keep track of the project personally. The reason is this: I use ErsatzMoco for my own purposes and intend to keep ErsatzMoco suitable for my needs. This does not mean I won’t add useful modules suggested - I just want to be able to fully understand the code in the future as well. In general I strongly prefer stability, readability and ease of use to speed optimization and ideology, if I may say so. For this very reason (and probably a bit of laziness too) I currently do not apply the full C++ code style with „auto“, smart pointers and so on but rather use some of the C++ advantages only. I understand if you dislike this mixed/dirty approach, there may be better solutions and I’m open for friendly suggestions of course, but please respect my choice of trying to keep things simple, even if you personally consider them out of style. I believe this makes the code more readable for people who have just started to tinker with the world of microcontrollers and Arduino. I do know this approach is potentially dangerous if not each and every pointer and variable is thoroughly taken care of, but in fact that kind of transparency is what I try to achieve in a C/C++ environment anyway. Please don’t expect perfection in any way - there may be shocking flaws I just overlooked, there may be weaknesses in design. There are even deliberate non-C++ pointer casts (OMG). The code released here works nicely so far but does not come with any warranty whatsoever.
The protocol provides a unified way of communication between devices using ErsatzMoco. That way your devices may easily be remote controlled. The protocol also works independent of the communication channel, so your devices may talk via RFM69 modules or serial connections (including ZigBee in transparent mode) or both. Other channels may be added in the future. Since the protocol is human readable and ASCII coded you may type commands into a serial terminal for debugging purposes.
Currently, the protocol offers the command modes „no acknowledge required“ and „acknowledge required“. If you send a command without acknowledge the message is just sent and not taken care of at all, so the command may get lost. Nonetheless this is the mode I recommend: During tests with my own devices I came to the conclusion it’s often helpful when your device communication setup allows for a „sloppy talk“, i.e. you send commands in intervals, and the recipient reacts each time or just when necessary. An example for clarification: If you build a remote controlled scale model for instance, it is not necessary that your model acknowledges the steering commands sent by the remote - if one command cycle gets lost, the next one will adjust your model’s motion still good enough. The resolution/accuracy of this approach is dependent on the polling/sending cycles, so you just need to choose reasonable update time frames. For model airplanes you will want a faster sending cycle, for slow boats much less command cycles will do. Communicating without acknowledge often allows for the best compromise between quick reactions and the freedom of each device to give other tasks greater priority when necessary. If you need your remote device to acknowledge a received command, you may adjust the number of retries. When all retries have failed you may implement an emergency reaction of some kind.
A valid ErsatzMoco message looks like this:
@EM:20:0025:0:Hello World:ME@
In generalized form:
Intro:CommandNumber:MessageID:ACK?:DataPayload:Outro
As you see, all message sections are delimited by a colon “:“. The character sequences „@EM:“ and “:ME@“ mark beginning and end of a message. This may look like overhead, but these long intro/outro sequences increase the stability of the communication and - most important - allow us to include almost any character sequence as data payload with a maximum length of 18 characters.
After the intro sequence the elements of the given example represent the following message content:
- 20 - number of the MocoCommand. This is a two-digit representation of an integer number. Thus, command no. 1 is sent as „01“ for example, and we can integrate 100 different MocoCommands in total. While this maximum number seems to be sufficient, future may prove that assumption wrong. An option would be to transfer the command number as a two-digit hexdecimal number (thus increasing the possible number of commands to 256), but I did not implement it this way because I felt it would make the messages less readable for beginning programmers. Maybe that’s wrong and maybe I should change the system to hexadecimal. All existing commands must be listed in „include/MocoCommand.hpp“. You may actually alter this number/name system according to your needs, but keep in mind your receiving classes need to react to the right commands and may require alteration accordingly. Currently, the example command number 20 sends a string message to be shown on a display.
- 0025 - unique ID of the MocoMessage. The ID is generated automatically by truncating the controller running time milliseconds to a four digit hexadecimal number. Thus, the IDs repeat about every 65 seconds. Since the (currently) only purpose of the unique ID is to identify temporarily stored messages that require an acknowledgement, this system should be sufficient since acknowledge messages are typically sent and repeated within a few milliseconds.
- 0 - ASCII representation of boolean 0/1. „0“ means „no acknowledge required“, a „1“ invokes the message acknowledge system.
- Hello World - the data payload, here a simple text to be displayed somewhere or to be used in some other way. This section is ASCII coded as well, so pure numerical data payload is always converted into ASCII numbers. The maximum length of the payload section is 18 characters/digits.
The length of the payload section and therefore the total length of a message is limited to prevent memory problems: Messages that require an acknowledge are temporarily stored in the sending device as well as in the receiving device as long as the acknowledge process lasts. The message buffer for this purpose is currently limited to a maximum of 10 messages. So don’t send a lot of commands requiring an acknowledge in a quick row: If the message buffer is full when another message needs to be stored, the first element in the buffer is deleted to make room for the new message. That way, the acknowledgement process is disturbed and acknowledge messages may not be sent or be ignored/not recognized anymore on the other side. That mechanism is a deliberate decision because of the limited RAM in microcontrollers: 10 messages take up 400 bytes, which is a lot. The maximum number of messages in the sender/receiver buffers may be changed, though - but be careful if you choose to increment the numbers at the top of the header file „ComModule.hpp“. In real life a maximum storage capacity of 10 messages should be sufficient as long as acknowledgements are requested only when they are absolutely necessary. If you use this mode sparingly and your communication channel is reliable, the acknowledgement process is finished so quickly that you should never run into problems.