Experimenting with minimal C++ on the Tiva C series Launchpad

Introduction

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.

Minimal C++

C++ can add a large memory overhead to your project. You can reduce this by removing dynamic object creation, exception handling and run-time type information. Losing these features isn't such a big deal for deeply embedded systems.

Blinky++

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.

The hidden "magic"

Before main is run, a certain amount of initialization must take place. This is handled in the function init in init.cpp

	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

Hardware initialization.

This turns the system clock up to its full 80Mhz and enables the hardware floating point unit

Data initilization

This section applies initial values to global and static variables by copying initial values from an area in the code (flash) into the ram occupied by the variables. This section also zeros out the memory used by other static and global variables that were not explicity initialized.

Construction of global and static objects

The linker generates an array of function pointers for the constructors belonging to static and global objects. This section calls each element of this array constructing the objects.

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.

From interrupts to objects

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	*/
	.
	.
	.
	.
};	
	

Comparison with the C equivalent

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 usageRam Usage
C++3108108
C286096

It would seem that C++ adds about a 10% memory overhead in this application. Is this a problem? That depends.... Is your hardware so constrained that every byte counts? If the answer is yes, then you are probably better off using C (or even assembler). If your hardware is not so limited then the situation becomes a little more cloudy. Do you find it easier to read/modify/extend C++ code for example? Is the C++ version significantly slower? Is there a portability implication? Only you and your project development team will know the answer to these questions, however I hope that having read the above you might at least consider C++ as a viable embedded language.

Downloads

Source code for the C and C++ version of the project is available here. You will need a suitable compiler and a TI/Stellaris Tiva TILM4F120 Launchpad board.

Comments

If you need to you can reach me at frank dot duignan at dit dot ie
Back to my ARM home page