Building Complex Systems with Microcontrollers
A simple button turning on a light is fine, but what happens when you want to build something a bit more complicated?
Aiming Higher
When starting out with the world of hobbyist/maker technology, your first project will probably include blinking an LED or printing to the serial monitor. This can be considered a simple system, which usually has a few inputs and a few outputs, such as a sensor turning on a buzzer. Generally, as a person gains more experience by making projects and seeing what other have done, they will strive to build more complex things. However, this prospect can be quite daunting, as leaping from a single sketch file to a multiple-class project hosted on Github is a monumental task.
Organizing Your Thoughts
Before you begin typing even a single character into an editor, write down the goals you have for your project. Think about what it should do, what features it will have, and how you can accomplish it. This can involve writing down a list of sensors you want, the board(s) needed, or what power sources will be used. On the programming side, you can break down the whole project into various modules, and by representing these modules as blocks, diagrams can be drawn that showcase exactly how entities interact with each other.
Constructing a Hierarchy
Now that your know the parts and features your project will have, it's time to make that plan a bit more concrete. After returning to where you wrote down each module, try to group it with other ones that share similar features. For example, you could group all sensors that use I2C into a common block, and then every sensor used could go into an even larger block. This diagram will eventually resemble a tree, where each parent element can have several more elements below that belong to it. By creating a tree, you can more effectively map out which classes you need to create.
Effective Use of Containers and Methods
In reference to the previous section, classes are a great way to encapsulate data and methods into distinct parts that, together, can all comprise a larger system. C++ supports inheritance, which, at a basic level, means that classes can get methods and variables from a parent class. This is especially useful when you need many different classes to support common actions, such as having all sensors be able to log their data to the serial monitor or update a struct. You can also create classes that contain objects from other classes, such as a LightController class that controls a NeoPixel object.
If you need to use complex variables or container-type instances in multiple areas, it's usually not a good idea to copy all of their values (deep copying) to an entirely new variable, as this wastes the already limited RAM capacity. Rather, make use of pointers that reference a specific object, just take care to delete objects after they're done being useful to avoid memory leaks and hanging pointers.
Keeping It Simple
A complex system is just what it sounds like- a system that has many different parts and can perform complex tasks. But, that doesn't mean everything has to be very complicated and messy. Below are three primary rules to help keep things neat and simple:
- Don't use magic constants: Instead of putting a number somewhere (or worse, in multiple areas), you should define it in an external header file or in a const variable.
- Don't pre-optimize: If you have a for-loop for example, don't try to do tricks with it yet (such as unrolling) until you have tested it with the rest of the code. Premature optimization can often lead to bugs that are very difficult to track down. Stick with what works at first, test, and then slowly change it to increase speed or save space.
- Use already-existing libraries (at least at first): If you have a sensor that already has a library and documentation (such as one from Sparkfun), try using that first. If you run into an issue with it, you can rely on support from other users and even the people who made it. This also helps to keep messy code at a minimum, as the developer who wrote that library most likely has more experience with that sensor.
In Summary
Effective and thorough planning is key when creating a more complex project. Diagrams are a great tool for this, as they provide a visual roadmap to guide your project's development. Keep in mind the standard practices for naming conventions, and make sure you stay consistent with them. Finally, keep your code portable and modular. Use the built in functions and provided libraries initially, and once you're confident they work correctly, you can move onto optimizing the code.