Folk,
I'm doing a little project, my first with Arduinos. I had assumed that the choice to use C++ meant that the APIs would be fancy object-oriented APIs that generate inline assembly code for performance. I normally do more bare-metal stuff, including building C++ APIs for the peripherals of the MC68HC11 more than a decade ago, so I was keen to see what can be achieve using more modern C++ compilers.
To say I've been disappointed is an understatement. The standard of the code is simply awful. The g++ compiler is fantastic, but the Arduino APIs just don't use that power.
As an example, "digitalWrite" takes over 50 cycles, compared to the expected 2. I know that there are libraries that work faster, but why are the default libraries so bad? Even calling these methods takes at least *three* times the code space that's required. I drilled in to see what's going on, but that's not the topic here. I wanted to show how things could be better, and to see if anyone here is interested in making it happen (personally I actually want to do this for ST's ARM range, but will assist if someone wants to do AVR versions).
Using template metaprogramming, we can get nice object-oriented APIs that also map directly to the hardware instructions. Unfortunately it's not easy to use the existing Arduino port definitions as template parameters, which might mean having to redefine some of the #defines of the low- level hardware (more below). So here's a minimal example that works, and shows what could be achieved by following this route:
template class Pin { public: Pin& operator=(bool b) { if (b) *(volatile uint8_t*)Port |= Mask; else *(volatile uint8_t*)Port &= ~Mask; return *this; } };
Pin portBp0;
Folk,
I'm doing a little project, my first with Arduinos. I had assumed that the choice to use C++ meant that the APIs would be fancy object-oriented APIs that generate inline assembly code for performance. I normally do more bare-metal stuff, including building C++ APIs for the peripherals of the MC68HC11 more than a decade ago, so I was keen to see what can be achieve using more modern C++ compilers.
To say I've been disappointed is an understatement. The standard of the code is simply awful. The g++ compiler is fantastic, but the Arduino APIs just don't use that power.
As an example, "digitalWrite" takes over 50 cycles, compared to the expected 2. I know that there are libraries that work faster, but why are the default libraries so bad? Even calling these methods takes at least *three* times the code space that's required. I drilled in to see what's going on, but that's not the topic here. I wanted to show how things could be better, and to see if anyone here is interested in making it happen (personally I actually want to do this for ST's ARM range, but will assist if someone wants to do AVR versions).
Using template metaprogramming, we can get nice object-oriented APIs that also map directly to the hardware instructions. Unfortunately it's not easy to use the existing Arduino port definitions as template parameters, which might mean having to redefine some of the #defines of the low- level hardware (more below). So here's a minimal example that works, and shows what could be achieved by following this route:
template class Pin { public: Pin& operator=(bool b) { if (b) *(volatile uint8_t*)Port |= Mask; else *(volatile uint8_t*)Port &= ~Mask; return *this; } };
Pin portBp0;
Note that the 0x25 is the memory-mapped address of PORTB (its I/O address is 0x05, but memory-mapping adds an offset of
0x20, if I understand the AVR hardware correctly).Now, when I write "portBp0 = 1;" I get exactly one instruction emitted ("sbi") which takes the expected 2 cycles (1 in -Mega). Same deal for "portBp0 = 0;", the instruction is "cbi". Both are single-word instructions, whereas a call to digitalWrite takes three or four words of code space.
Note that I would have preferred to define the template like this:
template class Pin {...};
Which allows removing the casts on uses of Port, but to be able to instantiate the template requires a cast:
Pin portBp0;
which translates roughly to:
Pin portBp0;
... and that's not valid for a template parameter. The only method I know that does work is to define the port variable as extern, in a particular section, and use the linker script or the linker option --just-symbols to define the location. This means we can also use a C++ reference instead of a pointer:
extern volatile uint8_t PortB; // address provided to the linker template class Pin {...};
Pin portBp1;
It's quite a lot of fiddling to use a linker script, but using --just-symbols is easy enough; either way you can't use the standard AVR header files for the values :(.
One option might be to define a structure for all the registers in a given AVR variant (and just locate the structure using --just-symbols), e.g.
extern struct { ... volatile uint8_t PortB; // ... at address 0x25 in the structure. ... } CPU;
void clear_B() { CPU.PortB = 0; }
The other advantage of using templates is that we can specialise them to set up the port correctly, and to check for collisions in port usage:
template class OutputPin : public Pin { OutputPin() { // (Check with a pin registry that this pin // isn't already assigned to something else?) // Set up port direction... } ... };
This also means that you can dynamically assign port pins just by defining a local variable in a function, and the pin will be set up for you when you hit that function.
With more work, you could set up templates for whole ports, or for ranges of pins on the same port:
template class PinRange { public: operator int() { return (Port&Mask) >> Shift; };
PinRange& operator=(int val) { Port = (Port&~Mask) | ((val