Hi all,
[disclaimer] Comparing to others here, this post assume you are familiar with the C++ language on a basic level.
I would like to share my thoughts about the topic in the subject: using C++ programming language for AVRs. Nowadays I am dealing with C++ during my daily work (on x86) and I was happy when I saw that avr-gcc actually can compile C++ code as well (avr-g++). So that in one of my recent AVR (atmega) projects I tried this new (at least to me :)) stuff. I will share some code design principles which I use often.
There are only a few reading available in the topic out there: link, link.
The warnings can be disappointing because you can not use a lot of cool stuff from the language constructs. Basically these are NOT available:
- exceptions at all
- missing new/delete, meaning you can not create object dynamically (technically there is no real heap in the AVR, I think this is the real reason)
- no STL (you can not create dynamic size containers like vector or map)
- possible performance drawbacks: codesize, implicit first parameter for the member functions (this), searching in the vtable in case of virtual functions takes time, etc
Looks like a painful list. Is there any remaining stuff which is still useful? I think yes. You can still design your code with true OO architecture. You can create interfaces through inheritance, encapsulate data into objects which can manage themselves, using templates(!), etc.
I’ve found a book which is about howto use C as an OO language. Respect for the people who can do it that way. Really :). I think you pretty much lose the type safety, and have to create for example the *this pointer explicitly as a function parameter for your methods. So the drawbacks are more or less reproduced by hand. If you don’t have the choice to use C++, than it is OK. I’ve read somewhere that the AVR community is pretty lucky as they have GCC which has very nice optimalization capabilities, so C++ code can be still very compact.
Example problem
Let’s see a typical problem. A very general stuff in embedded systems to measure time with HW timers. For example you want to count that how many times a button was pressed, connected to an external interrupt or GPIO pin. Let’s also assume that this button is mechanic and is bouncing. You can solve this several ways by hardware improvements (internally debounced switch, capacitor, etc.), but obviously the cheapest and most elegant way is to do it in software. The theory is that you should know the maximum time how long the switch can bounce (or you just go with a big number). This time have to be less than how quick you can actually press the button twice. Let’s say we have a GPIO pin with internal pull-up and we have a switch connecting the pin to GND. In this case, pressing the button will cause a 1-0 edge on the pin + some bouncing. Of course the first 1-0 edge is used to trigger the button functionality in the SW. Here we will also start a time measurement with the desired maximum bounce time. During this period, the subsequent 1-0 edges should be ignored. Looks like this is how to solve the issue. Well, it is still incomplete :). I’ve met some buttons, which can bounce also when you release them(!). Pretty hard to imagine how it is possible after you saw some disassembled button, but this is real. All-in-all after the bounce is corrected when the button is pressed, you have to wait for the first 0-1 edge also which means the button is released. This time you also have to start the “ignore” timer, because the second bouncing will cause 1-0 edges also. This would cause that the SW think the button is pressed again.
Let’s draw boxes and lines :)
Let’s address this problem with OO in mind! On the lowest level we need an entity, which can debounce the switch. My first solution with C was pretty much always to have a volatile global variable, which holds the last state of the switch (0, or 1) and a timer, used to count the ignore time. Than the handler function is directly called when the 1-0 is detected. This has a some problems: what if you introduce another switch for example. Or five :). Your debouncer code should know when to call who. This is bad. We will use dependency inversion here.
First of all, uchar is a shorthand for unsigned char. It could be a bool as well. The gray background part is fully reusable. The Debouncer class is intended to fully handle the task. The handler entity is not directly called from the Debouncer because it would mean that the Debouncer must know who is the user. The shared entity which both parts are rely on is an interface, called DebouncerUser. This class has only one pure virtual method, which the real handlers will implement and the Debouncer will call.
Going deeper
Our debouncer is well designed. Even when you feed your debouncer logic from an external interrupt vector and not by polling your pin state from time to time, you still need a timer, to measure when bouncing is over. Now we need a common way to handle timer events. Basically what you get from the MCU is that it can notify your code after a desired amount of time. You can even adjust the required notify time between two interrupt, but than you loose the option to handle more than one event with a single timer.
I would expect an these options as a timer user:
- I may be interested when my timer expires
- In several use-cases I may be also interested when someone started the timer, or even when canceled
- I would also like to inspect the remaining time slice
- Or even change the remaining time
Furthermore as the owner of the application, I would like to organize these timers into sets, controlling the whole set in one single call in the HW timer interrupt, which has a static setting of overflow time.
Assume our Debouncer logic is such timer user:
The pattern is the same as before. Our debouncer itself is a SoftTimerHandler. It implements the handler functions. The SoftTimer implementation allows to any handlers to subscribe the event, when the timer is set, reset, or when timeout happens. It may also alter the time value when it is set. Furthermore, the user can access the timer interface directly through the handler (with the getTimer method). This means that even the handler code can set or reset the underlying timer when needed.
Right, but show me some code :)
The user code of such a debouncer can be the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <avr/io.h>
#include <avr/interrupt.h>
#include "Debouncer.h"
Debouncer *gDebouncer = 0;
#define INIT_TIMER0() TIMSK|=1<<TOIE0;
#define INIT_INT0() PORTB|=1<<2;MCUCR|=1<<ISC01;GIMSK|=1<<INT0;
#define BUTTON_PORT (volatile void *)&PORTB
#define BUTTON_PIN_MASK 1<<2
ISR(TIMER0_OVF_vect) {
gDebouncer->getTimer()->tick();
}
ISR(INT0_vect) {
gDebouncer->inputChanged();
}
class MyButtonHandler: public DebouncerUser {
public:
void buttonPressed() {
//do_the_real_business();
}
};
int main(void) {
INIT_TIMER0();
INIT_INT0();
MyButtonHandler button;
Debouncer debouncer(BUTTON_PORT, BUTTON_PIN_MASK, &button);
gDebouncer = &debouncer;
sei();
while(1);
}
I would say it is clean, at least :). In the #define list you configure your HW timer according to your needs, also the port and pin settings. We implemented the user code in the MyButtonHandler class, by inheriting the DebouncerUser interface. The buttonPressed function will be called when needed, without any bouncing. In the main() we only need to initialize things (timer and INT0 here) as usual, than the point is to wire the handler and the Debouncer object together, activate the interrupts, and that’s it. There is a global debouncer object pointer which is set in main(), to allow the usage from the timer interrupt.
But where is the volatile flag from the global stuff???
Okay, so when one starts to hack with small MCUs like AVRs and like me :), the first thing which is learned that if you don’t put the volatile notation in front of global variables, sooner or later strange things starts to happen. Than you realize that it is needed for the cases when you access the variable in interrupts. But that it only one half of the truth. Volatile exactly means that you tell the compiler, that this object may be accessed at any single point of time, so please avoid any optimization on it. Such optimization can be like if you access your variable for writing in your main() in a loop (e.g. counting stg.), the compiler makes the asm code so that it won’t save the data back to RAM, instead it keeps it some register for faster access. In this case if an interrupt comes, it won’t know that actually main() is running and has a modified instance of the variable in some register, so will fetch the original value from RAM, based on the logic maybe modify it and save back. Than the strange thing happened :).
When I started to deal with C++ on AVRs, I realized that objects with the volatile flag actually not work very well. I got strange compilation errors about discarded qualifiers and similar things. So the point is, if we can avoid to the access of the same variable/object from non-interrupt and interrupt related code, we can omit the volatile flag. This is exactly true if you can design your code so that it is totally event based, like this example. Nested interrupt is also not the case by default on AVR MCUs, you have to explicitly turn them on, but it is very rare.
Conclusion
I found C++ very useful when heavy instruction and CPU cycle counting is not relevant for the performance. The code above compiles into 500 byte code and 26+2 byte data for an ATTINY45. Using this concept never took me to hit the limits of the AVRs.
I’ve started to collect my base library stuff like this debouncer on github, here.
Cheers!