I'm not a big fan of the '->' notation used with pointers to C strucures. I also like the data hiding that C++ provides (I know that this can be more or less done with C too). I thought it would be interesting to experiment with using a minimal C++ program on the Tiva Launchpad to see what was involved.
The traditional "Hello World" (first program) for microcontrollers typically toggles an LED. This program brings that a little further in an attempt to display more features of the launchpad board and to make the project size a little more realistic. The program checks which buttons you press, (SW1,SW2, both SW1+SW2) and turns on different LED's in response. The program also uses the virtual serial port provided by the debug interface to output the current contents of the GPIO port F.
#include "tilm4f120hqr.h" #include "leds.h" #include "console.h" #include "buttons.h" // Simple blinky example cButtons buttons; cLeds Leds; int main() { int count = 0; console.print("Hello World"); while(1) { Leds.allOff(); if (buttons.button1()) Leds.setRed(1); if (buttons.button2()) Leds.setBlue(1); if (buttons.button1() && buttons.button2()) Leds.setGreen(1); console.print("GPIOF: "); console.println((int)GPIOF_DATA); } return 0; // should never get here }
Three global objects are used by the program: console,Leds and buttons. The constructors for these objects initialize the port hardware appropriately. The Leds object encapsulates the behaviour of the 3 LED's on the board and provides a set function for each allowing them to be turned on or off. The buttons object provides two methods button1 and button2 that return true or false depending on whether the button is pressed or not.
void init() { // do global/static data initialization unsigned char *src; unsigned char *dest; unsigned len; // Initialize hardware initFPU(); initSysClock(); // Put initial values into initialized global/static variables src= &INIT_DATA_VALUES; dest= &INIT_DATA_START; len= &INIT_DATA_END-&INIT_DATA_START; while (len--) *dest++ = *src++; // zero out the uninitialized global/static variables dest = &BSS_START; len = &BSS_END - &BSS_START; while (len--) *dest++=0; // Need to call on the constructors of any global/static C++ objects fptr *ConstructorsStart; ConstructorsStart = &__ctors_start__; if (*ConstructorsStart != (fptr )-1) { while (ConstructorsStart < &__ctors_end__) { (*ConstructorsStart)(); ConstructorsStart++; } } main(); }The function is divided into various sections
A few other bits are required to build the C++ project. These are functions that would typically be provided by an external library. These functions support things like stack unwinding which may be useful in certain debugging and exception handling instances. I'm not interested in this behaviour here so "dummy" (blank) version of these functions are appened to the end of init.cpp. This reduces memory size considerably.
Dummy versions of malloc, free, new and delete are also provided which again reduces code size and underlines that this is project will not do dynamic memory allocation.
This program creates a console object to handle interrupt driven serial communications.
It would be nice if the interrupt service routine for the console object could be declared
within the console class however there are a couple of problems with this.
1: The name of the interrupt handler is usually "decorated" by the C++ compiler which
means that the symbol representing it can be hard to determine. You need to know this symbol
if you want to fill out the interrupt vector table.
2: The data members of the object are created on a per-object basis.
The compiler can't figure out at compile time where the data members are (which object?) so any references
to member variables will fail. In the case of regular member functions this problem is avoided
because the function receives a hidden parameter: the this pointer which is used
as a base address to access member variables. Interrupt service routines are not "called" in
the traditional sense so there is no this pointer.
The solution here involves a friend function. Friend functions are permitted to
access private member variables of objects with which they are friendly. A shortened version
of the console class definition is shown below. The friend function isr_uart
is the interrupt handler. It accesses the private member functions of the global console object
that deal with data reception and transmission. Using friend functions like this allows you limit
the visibility of critical member functions and data.
class cConsole { public: cConsole(int BaudRate); // constructor friend void isr_uart(void); private: int ComError,ComOpen; cCircularBuffer TXBuffer,RXBuffer; void usart_rx (void); // Handles serial comms reception void usart_tx (void); // Handles serial comms transmission }; cConsole console(9600); // create the console object void isr_uart(void) { // check which interrupt happened. if (UART0_MIS & BIT5) // is it a TXE interrupt? console.usart_tx(); if (UART0_MIS & BIT4) // is it an RXNE interrupt? console.usart_rx(); }The interrupt vector table looks like this (truncated for brevity):
void init(void); void Default_Handler(void); typedef void (*fptr)(void); // The following are 'declared' in the linker script extern unsigned char INIT_DATA_VALUES; extern unsigned char INIT_DATA_START; extern unsigned char INIT_DATA_END; extern unsigned char BSS_START; extern unsigned char BSS_END; extern fptr __ctors_start__; extern fptr __ctors_end__; extern void isr_uart(void); int main(void); // the section "vectors" is placed at the beginning of flash // by the linker script const fptr Vectors[] __attribute__((section(".vectors"))) ={ (fptr)0x20008000, /* Top of stack */ init, /* Reset Handler */ Default_Handler, /* NMI */ Default_Handler, /* Hard Fault */ 0, /* Reserved */ 0, /* Reserved */ 0, /* Reserved */ 0, /* Reserved */ 0, /* Reserved */ 0, /* Reserved */ 0, /* Reserved */ Default_Handler, /* SVC */ 0, /* Reserved */ 0, /* Reserved */ Default_Handler, /* PendSV */ Default_Handler, /* SysTick */ /* External interrupt handlers follow */ Default_Handler, /* IRQ 0 */ Default_Handler, /* IRQ 1 */ Default_Handler, /* IRQ 2 */ Default_Handler, /* IRQ 3 */ Default_Handler, /* IRQ 4 */ isr_uart, /* IRQ 5 UART0 */ Default_Handler, /* IRQ 6 */ . . . . };
How does the C++ version compare with the C equivalent? We could compare it under a number of headings: speed, flash usage, static ram usage, maintainability etc. I've looked at just two of these: flash and static RAM usage. The results are (in bytes) are shown below.
Flash usage | Ram Usage | |
---|---|---|
C++ | 3108 | 108 |
C | 2860 | 96 |