There has been a big explosion about using C++ within embedded systems. Recently David sent me some interesting papers and info about Embedded C++ so here I present what I've been doing so far (or at least a small portion of it).
Templates
First I have to say that templates are not a code bloat if they're managed with care. In fact the C++ compiler is supposed to optimize them at compile time.
Here are some base templates I've coded to ease pin mapping on the LPC23xx ARM family.
#include "lpc24xx.h" /* LPC23/24xx register definitions */
#include "static_assert.h" /* static assert for non-C++0x compilers */
namespace HW
{
/**
* Output Pin Abstraction
*/
template<unsigned int port, unsigned int pin>
class OutPin
{
STATIC_ASSERT(pin <= 31, PinMustBeLessThan32);
public:
OutPin() {
*(&FIO0DIR + 8*port) |= (1<<pin); //configure pin as output
}
void Set(void) { *(&FIO0SET + 8*port) = (1<<pin);}
void Clr(void) { *(&FIO0CLR + 8*port) = (1<<pin);}
};
/**
* Input Pin Abstraction
*/
template<unsigned int port, unsigned int pin>
class InPin
{
STATIC_ASSERT(pin <= 31, PinMustBeLessThan32);
public:
InPin() {
*(&FIO0DIR + 8*port) &= ~(1<<pin); //configure pin as input
}
operator bool () {
if ( (*(&FIO0PIN + 8*port)) & (1<<pin) )
return true;
else
return false;
}
};
};
This may look a like waste of code at first glance. However these classes avoid many mistakes and provide a hardware independent interface to pin outputs and inputs, it's just a question of redefining this templates to fit the platform.
Also note that the compiler will throw an error if pin's value is not within the allowed range. This is also a protection and won't waste any processor instructions or any other memory. It would be useful to limit the port range too. The above templates use the static assert method described earlier in this post.
The constructors will manage to configure the port pin as output or input. If the pin is defined globally then the constructor will be called before entering the main function, configuring the pin as it should. If it is defined inside a function or by using the new operator (which I would try to avoid) then the constructor will be called when it is instantiated.
Here is an example:
// Pin 0.25
HW::OutPin< 0,25 > myLed;
// Pin 1.20
HW::InPin< 1,20 > mySwitch;
void invert(void)
{
if ( mySwitch )
myLed.Clr();
else
myLed.Set();
}
Beautiful, isn't it? The generated code (arm-elf-gcc 4.2.x) is exactly the same as if it is done manually by writing/reading the corresponding registers. There is no loss in performance compared to the equivalent C code.
In the next post I will discuss a UART implementation by using similar code constructions.
What I do not like about C++
C++ lacks many useful C99 characteristics and g++ doesn't implement them either. I could live without most of them but what really hurts me is to avoid using Designated Initializers, specially on structs. It won't be a big problem if struct's data is on RAM but it's disappointing when structs are on ROM (const).
If we want a struct to be in ROM while using C++ it has to be declared const, but in addition constructors can't be used or the const-declared struct will be placed in RAM (cRazY). It is an understandable limitation but that means that the only way to initialize a struct is by passing each element one by one and in the exact order as they're defined in the struct. That is error prone, particularly when a new element is added in the middle of the struct. So when we try to switch to C++ to avoid errors we become prone to issues that were solved with C99.
So, that's the only thing I don't like about C++, the solution? Use C for cont struct initializers and C++ for the rest? Don't know.
No comments:
Post a Comment