This is the third part of the meta-tutorial, where I talk about designing a cheap plant watering sensor. If you did not already read the first and second part, please do it now. These parts contain a lot information which lead to this point of the tutorial.
The second part ended with step 14, designing a first prototype PCB. So let us start with the next steps in this journey. This article will be the smooth transition from prototyping to the initial planing for a final design.
Step 15: Assemble and Check the Prototype
After receiving the prototype PCBs from OSH Park, I assemble one completely, including the cable and with one of the sensor plate prototypes as foot part.
Set the Fuses of the Microcontroller
The microcontroller
ATtiny13A requires programming using SPI before it can be soldered to the board. There are special bits in the memory, called “fuses”, which control very basic settings of the chip. One of this fuse controls if the chip can be programmed and debugged via the
debugWire protocol. This protocol just uses one single wire to program and debug the chip, bus has to be enabled first.
So I put the microcontroller into the programming adapter and connect everything via the Atmel ICE to the computer.
Next I open the device programming tool in Atmel Studio and change the fuses to the required values. If there is already a firmware available, the studio will automatically set the
debugWire fuse, but not the other ones.
In the image above, you can see the fuses as they are set as factory default. In the next image you can see the fuses set for the device. As you can see, there is a exclamation mark near the
DWEN fuse for the
debugWIRE functionality. This flag is usually set automatically be the IDE if you program the microcontroller for the first time.
Solder and Assemble the Device
Now I can solder all components on the device and connect the wires to the foot. Here I use the prototype sensor plates, cut just above the plates. The wires are 20cm long, connecting the two parts. To protect the contacts on the foot part, I just use some insulation tape. This is not ideal, but ok for this prototype.
Test Everything
Finally I can verify if there are any great differences between the breadboard prototype and the PCB version of the circuit. I am especially interested in the power consumption of the device. I explained the measurements in this blog post in detail, here just the summary.
The power consumption is very close to my initial measurements and no big surprise. With the planed frequency of one measurement (and flash) every 10 seconds, the battery life of approximate one year is feasible.
The frequency of the oscillator is almost identical as in the prototype. Also the two wires do not alter the frequency if touched, or just in a very subtle way. Another issue would be a low frequency radio emission from these wires, which could for example conflict with some time signals sent on this low bands (e.g. DCF-77). I lack the measurement equipment for this task. What I could test, are inferences on mobile and wifi bands. Here I did not found any inference.
Theoretically I would need a wire length greater than one meter to produce a reasonable RF signal in the kilo herz range – but this is clearly no topic I have in-depth knowledge. Maybe someone can give insight, if the generated rectangle waveform in the 90-200 kHz range could generate a signal, which interferes other devices.
After we got proof that our device is feasible and will actually work, we need a beta firmware, which will be near the final one to continue our testing. Also we need to think about user interaction and how the user will work with the device.
Step 16: Plan the User Interaction
As planing for a final device starts, I will think about the user interaction which will define more details. Like the circuit diagrams for electronics, there are UML diagrams to plan use cases and work flows. Elements in circuit diagrams were standardised to allow a simple and easy information exchange between engineers. If you once understand the elements in a circuit diagram, everyone with the same shared understanding can read them. It is a sort of language you can speak and understand.
For other tasks, like use cases, work flows and software there is another language, called UML (Unified Modeling Language). If you do not speak this language yet, I would recommend you to learn it. You will be able to express more details of your designs in a standardised way other engineers will understand immediately.
The Use Cases
For our plant watering sensor there are a number of actors: The user, the plant and the battery. Each of this actor uses the device to accomplish something (the use case).
All of these use cases should be covered by our device and therefore by the firmware in our device. Our design has one single button, but we have two distinct actions to cover. The first draft, where the duration of the button press selects the action worked well for me. So I will keep this behaviour for the firmware.
Define the Push Button Behaviour
After defining the use cases I define the push button behaviour for the two user actions.
All cases start with a pressed button. This is the only way to enter the configuration mode of the device. In the first case, the user pushes the button, but releases it in the first phase. This will abort the configuration immediately and allow for a quick check if the device is working.
In the next case, the user pushes the button until the second phase begins and releases the button there. This will write a new set-point to the device.
The last case is, if the user keeps pressing the button. After the end of phase two, the LED flash is disabled. The user gets the success display, as soon phase two ends, even if he keeps pressing the button as shown in the last case. It will lead to the same result.
Design the Firmware Behaviour
If your firmware is as small and simple as the one for the plant watering sensor, best is to draw a simple control flow chart. This chart will give you a quick impression of the complexity of the system you plan to design. In the case, you struggle to keep your control diagram small, the firmware will get complex. If your control flow will fit on one single page of paper and cover all use cases, you can expect something simple.
Note this is the UML activity diagram style. In this basic form, it is very similar to the “old style” flow charts you see everywhere. However the UML language provides a more detailed specification which can be handy if you like to express things like interrupts and multithreading.
Conclusions about the Display
After drawing the diagram, I realised how many different display situations I have:
- Need for watering.
- Low battery.
- Configuration phase 1.
- Configuration phase 2.
- Successful set-point action.
- Successful disable action.
While there are endless possibilities to control one single LED to display different things, it may be difficult for a user to understand all this flashes and pulses correctly.
Step 17: Review the Current Device
The title of this step should be: “Review the current device with all the gathered knowledge to this point”. Actually you can add this step almost everywhere, but I added it at this point, because after the review I found the need for some hardware changes.
One single signal LED is not enough for all the display states, a second one in a different color would be nice. Adding an additional LED to the same microcontroller pin is no problem.
Each LED has a minimum forward voltage. If the voltage is lower than this minimum, the LED will not light up. Two LEDs in series, both with a minimum forward voltage of 2V will not light up if the supply voltage is 3V. Adding the two resistors to the design as shown is adding an additional voltage drop.
If the microcontroller pin is left as input (Hi-Z), no LED will light up. If the pin is programmed as output in high state, LED 1 will light up. As output in low starte, LED 2 will light up.
While reviewing the display situation of the plant sensor, I also found the through hole LED will make no sense, especially if there are two LEDs required. Therefore I will focus on SMD LEDs for the final device. This will have a number of positive influences to the project:
- The LEDs require less space, the two LEDs can be placed very close in the center.
- No holes are required at the center of the PCB, therefore the battery can be placed closer to the center which will reduce the overall size of the PCB.
There are two colour SMD LEDs, this would be another advancement to further reduce the size. Currently there is no need for a two colour LED.
Modify the Prototype
To work on the firmware, the PCB prototype needs to be modified for the new two LED system. This can be done easily using our current prototype.
Here I soldered the two SMD LEDs onto the PCB, a green and a red one. I replaced the resistor on the other side with a small wire bridge. Luckily there was this trace with the supply voltage nearby, otherwise I had to drill a small hole for a wire through the PCB.
Step 18: Plan the Final Firmware
Writing software is simple, easy to learn and everybody can hack a simple program after a few days. Though writing a reliable, extensible and maintainable software needs experience and time. The most important aspects of a software are:
- Maintainable
- Extensible
- Reliable
But what do these terms mean? Let me quickly go through these three important aspects of a software and explain why they are important.
Maintainable
Even if you write software that should last, there will be the need of an update or fix of a problem. This usually does not happen while you write this software and still have every detail of it in your mind. It will always happen in the worst situation.
Now you have to go back to this software you wrote, possibly years ago, and add some fix for a problem that was not noticed for a long time. All the time you invested into the quality of your code will immediately pay out now.
The newbie/careless/amateur: He will look at a huge pile of junk. The code has no structure and no documentation. It will take him days, or even weeks to fully understand his code again. All this for a quick fix, adding a few lines. It is very likely, because under time pressure, the hacker will not go through the whole way to fully understand his old code, the applied fix will infer with other parts of the code and while fixing the first problem – it will introduce two new problems to be discovered in a few months.
The pro (ideally you!): He will look at well structured code. After a quick read through the documentation, he will easily identify the section where to apply the fix. The comments in the code are really helpful. After a few hours, the fix is applied and all tests are done.
Extensible
The word extensible also stands for portable in the special case of firmware. Even your firmware works perfectly without any issues, there is always a customer which really needs a special feature which is not implemented.
Now you have go back to this software you wrote, possible years ago, and add some new feature which is probably stupid and just to be implemented to keep the customer happy. All the timer you invested into the quality of your code will immediately pay out now.
The newbie/careless/amateur: He will look at a huge pile of junk. It would take him days to understand his own undocumented code. So he just hacks the new feature somewhere into the code and makes the pile of junk even worse. Because he did not fully understand his old code, he also adds some new problems to be discovered in a few months.
The pro (ideally you!): He will look at well structured code with modules encapsulating the different aspects of the system. The documentation is really helpful to point to the right modules for the extension. Either he adds a new module for the new feature or finds a module to extend. After a few hours, the new feature is implemented and all tests are done.
The new feature does not work with the used microprocessor anymore. It is too slow and lacks the required memory. You have to choose a different one, different registers and a different architecture.
The newbie/careless/amateur: He has to write a new firmware from scratch. He does not understand his old firmware anymore, so he does not care. Anyway, he is very quick at writing undocumented unstructured code…
The pro (ideally you!): After a very long sigh, you identify the hardware abstraction layer you wrote and replace the implementations of these methods. Done!
Reliable
Reliable software is constantly ask the question “What if?”. The software should not only run perfectly under normal conditions, there should be no situation where the software developer gives the control out of his hands. There are obviously unexpected situations which you cannot plan in advance. If the microcontroller suddenly has glitches, you are never prepared to this – but still, using watchdog functionality or just if you actually consider this situation can make your device more reliable.
The newbie/careless/amateur: He will just implement exactly the required functionality to bring the device to life. If some conditions change, his software will fail with unexpected results.
The pro (ideally you!): You will brood over every
if,
for and input – imagine worst case scenarios and prepare for them. You also always find clever ways to plan for unexpected situations without bloating your code.
Start with Modules
Before I start the actual software design, I think about the modules I need for the design. I use the term modules, as a general term for encapsulated functionality of the software. A module will have a defined interface which hides the implementation from the caller. Each language has its own concepts of encapsulation. Also depending on the size of a software, each module is divided into smaller units, which also can be divided into smaller units.
This first design aspect should only focus on the top level structure of the software. If required, these top level modules are refined in a later step. Each iteration will refine more details, until a complete software design is created.
For our simple software, this top level structure is already enough for the design. The maximum I could do is defining the whole interface of each module. This is not necessary for a simple software like this.
As you can see, the Logic module is the core of the firmware. This module contains all the control flow of the software. It uses a Setting module which is responsible for the permanent storage of the set-point. There is also a Display module, which is responsible to generate all the visual LED effects for the user.
The Hardware module is the abstraction layer to the hardware. It will contain functions like
setLedState() or
getVoltageValue() which work on the hardware, without hardware specific details on the interface. If the hardware changes, just the implementation of this modules needs to be changed. Everything else will still work as expected.
There is also a Configuration module. It contains static global configuration values which are used by other modules. The minimum voltage value is an example. You can change it at this central place and do not have to search trough the code to find the place where it is actually tested. This module also contains flags, to conditionally add or remove functionality from the firmware, which is important for testing.
The Tools module contains functions which are used trough the whole code, but are in a way abstract, like a
delay function.
Consider the Available Programming Languages
Luckily there are a lot of options to program the
ATtiny13A. Atmel provides a very good and free IDE to program these chips. It is only available for Windows, but is is absolute no problem to use a Virtual Machine to run this IDE. I actually would never run Windows directly on any machine, but this is another matter.
If you consider the available options, you should always check how modern the used toolchain is. Today you should at least expect a C-compiler supporting the C11 standard, and for a C++-compiler you should expect a complete C++11 standard support. If the manufacturer of a microcontroller tries to fob you of with an ancient dinosaur compiler, please consider switching to another microcontroller where you can use a modern compiler!
A negative example is Silicon Labs: They produce excellent 8-bit microcontroller, also a modern platform independent IDE called Simplicity Studio – but under the hood, there is the Keil compiler – which feels… dusty.
The compiler is the tool which will do most of the work for you. It will eliminate all unnecessary parts of your code, it will optimise the last bit out of your functionality to make the best use of the microcontroller and it will let you do high level programming to produce simple, clean code which is easy to maintain. In the best case, you never have to optimise something by hand.
Modern compiler also support newer language features, which were integrated into these compilers to make the life for programmers simpler and the code safer. They also produce more warnings and better error messages.
Some examples for the C99 and C11 standard which improve microcontroller programming:
- C99: The
header, which introduces
boolas native type.
- C99: The
header, which introduces
int8_t,
uint16_tand similar types.
- C99: Variables can be declared at every place in the code.
- C99: One line comments using
//
- C11: The
_noreturnfunction specifier. You can tell the compiler that a function never returns.
- C11: Static assertions, let you check conditions at compile time.
Some examples for the C++11 standard, which improve microcontroller programming:
- C++11: The
constexprkeyword, allows to write functions which generate constants at compile time.
- C++11: Initialiser lists. Initialise a complex
structwith default data, directly stored in flash memory.
- C++11: The
autokeyword. This will make your code more universal, in case some data types change.
- C++11: Range based loops. They will also work for C-style arrays.
- C++11: Lambda functions and expressions. Writing a library which provides a callback was never easier.
- C++11: Null pointer constant
nullptr. A type safe null pointer.
- C++11: Strongly typed enumerations. Now you can make sure, your
enumis a 8-bit or 16-bit value and easily convert these values.
All these improvements have a real impact on microcontroller code. They help you to write better and more efficient code. If you are forced to use a very outdated compiler, your code quality will suffer.
If your C-compiler forces you to declare all variables in a function, at the begin of the function, it will at least have an impact on the readability of your code – and you may be tempted to write the code in a different, less transparent way.
Decision for a Language and Style
The C and C++ support of the Atmel Studio for the
ATtiny13A chip is a little bit outdated, with gcc version 4.9.2 from 2014 – but compared to other vendors, this is a very modern – and there are most of the C++11 features available.
Therefore I will write the firmware in C++, but using a strictly procedural approach without classes to waste no byte. I will use functions in namespaces to keep the code readable and clean.
Epilogue
Thank you for reading this article! I hope it was insightful and will help you with your own projects. This part took a little bit longer to write, because I always found some extra information I did like to add to the text.
In a few days I will publish the fourth part, which will focus on writing a preliminary firmware and do usability testing and do some missing electrical tests.
Read part four of this article.
If you have questions, miss some information or just have any feedback, feel free to add a comment below.
Have fun!
1 thought on “How to Design a Cheap Plant Watering Sensor (Part 3)”