Been doing this for a while now. I’ve heard it called “C+”, essentially meaning that you use only a chunk of the features available.
Features I do use:
- classes, abstract classes, singletons
- pass by reference
- single inheritance
- templates
- namespaces
Features I don’t use
- template metaprogramming (I don’t use this *ever*, anyway)
- new
- exceptions (sometimes)
An excellent library is ETL. It gives you statically allocated versions of the stuff in the STL.
Yes, I think this is a nice summary how to use C++ effectively for embedded programming. I personally also use std::function quite as a
local callback to the application. When paired with a template this can be rather efficient as well:
template <typename T>
static void RMW32(volatile void* addr, T operation)
{
volatile uint32_t* addr32 = reinterpret_cast<volatile uint32_t*>(addr);
uint32_t rd, wr;
do {
rd = __LDREXW(addr32);
wr = operation(rd);
} while (__STREXW(wr, addr32));
}
Which then used with type Tatomic:
Tatomic::RMW32(addr, [clr, set](uint32_t value) { return (value & ~clr) | set; });
This statement is then repackaged twice (first SetClear operation, then as GPIO SetPinMode) for application code. Don't notice any overhead in disassembly output with -O2:
8020342: 40a2 lsls r2, r4
8020344: ea6f 0c05 mvn.w ip, r5
8020348: e856 4f00 ldrex r4, [r6]
802034c: ea0c 0404 and.w r4, ip, r4
8020350: 4314 orrs r4, r2
8020352: e846 4700 strex r7, r4, [r6]
I think these videos are also a nice demonstration about
negative cost C++ programming:
https://youtu.be/fRzF7NSF1IIhttps://youtu.be/u615he5wdeoAlso episodes 54 thru 56 show "zero cost" C++ embedded programming
I think the added safety of C++ type system, as well as added abstractions can speed up development massively. For me also C++ code is much easier to unit test than C. It's easier to instantiate a class locally than it is to work with a C module that has uses some local storage, which you cannot reach to reset/instantiate for testing.
The piece of code I just demonstrated was from a small toy program playing around with these C++ definitions. I had created a Stm32Gpio object that can be used as in/out, write pins, etc. From that I created a bitbanged SPI driver using an abstract GPIO objects, not tied to the STM32 implementation.
Using that SPI driver, it's trivial to define a GPIO expander driver using a 74HC595 or a fancy SPI chip, which can then be used as GPIO over SPI. So basically we've gone full circle back to abstract GPIO objects again, but this time communicating over SPI, transparently. One danger ofcourse is that this detail is abstracted away, then any synchronous GPIO operation becomes very slow. Since most code is written synchronously to deal with GPIO, the application scope is rather small. You could ofcourse implement caching behaviour (e.g. accumulate R/W operations and then commit them all at once), but such caching and requiring flushing is not application agnostic, and thus falls flat down on it's face. I think there are other more higher-level applications that can make better use of abstractions though, e.g. memory devices or sensors drivers.
Nevertheless, it was still a funny exercise. Because the SPI GPIO's are "basic" GPIO's again, one could create a bitbanged SPI driver using GPIO that is already going over bitbanged SPI. I cascaded this with 5 chips. Since each GPIO write took 16 bits to transfer, it takes 16^5=1048576 clocks to write a single GPIO at the end of the cascaded chain (a few more actually because of latches or chip selects). With an average SPI clock of 10MHz on a STM32F4, that comes down to a blink rate of about 5Hz when it's running in a while(1) {} loop continuously.