2015-04-30

Building With LLVM

Since I began this little endeavor a few weeks ago, I've had a lot of fun and learned much about embedded systems and low-level programming. I started out with a simple Arduino program and kept chipping away small pieces, until I had removed all third-party code. All that was left was a single C file, giving me full control over my Arduino Due.

Now that this first milestone is reached, I have chosen another goal to work towards: While I like C quite a lot, I like Rust[1] more, and I'd like to use it for my future embedded projects. So this will be the main topic for the next articles: Rewriting our C program in Rust, and then cleaning it up. Before I attempt something more complicated than blinking an LED, I'd like to build up some abstractions that allow me to write clean and reliable embedded code in Rust.

Since the Rust compiler is based on LLVM I thought it might make the transition easier to use LLVM's C compiler, Clang, to build our C program. This didn't require any modifications to the C code, but for the sake of completeness, here's the program again:

// This is the top of the stack, as provided to us by the linker.
extern unsigned int _estack;


// This is a partial definition of the vector table. It only defines the
// first two entries which, as far as I can tell, are the minimum needed
// for a program to work at all.
// Space for the other interrupt handlers is reserved. I'm not sure if this
// is necessary, but I can imagine that the vector table not having the
// right length could cause all kinds of problems (imagine if it was too
// short, and the linker would place something else directly after it).
typedef struct {
	void *initial_stack_pointer_value;
	void *reset_handler;

	char other_interrupt_vectors[44 * 4]; // space for 44 32-bit pointers
} VectorTable;


void start();


// The vector table. We're using GCC-specific functionality to place this
// into the .vectors section, not where it would normally go (I suppose
// .rodata). The linker script makes sure that the .vectors section is at
// the right place.
__attribute__ ((section(".vectors")))
const VectorTable vector_table = {
	(void *)(&_estack),
	(void *)start,
};



// Addresses of several registers used to control parallel I/O.
static volatile int * const pb_pio_enable          = (int *)0x400E1000;
static volatile int * const pb_output_enable       = (int *)0x400E1010;
static volatile int * const pb_set_output_data     = (int *)0x400E1030;
static volatile int * const pb_clear_output_data   = (int *)0x400E1034;

// Bit mask for PB27. This is pin 13 (the built-in LED) on the Arduino Due.
static const int pb27_mask = 0x08000000;

// Addresses of several registers used to control the real-time timer.
static volatile int * const timer_mode_register  = (int *)0x400E1A30;
static volatile int * const timer_value_register = (int *)0x400E1A38;


// As the name suggests, this function sleeps for a given number of
// milliseconds. Our replacement for Arduino's delay function.
void sleep_ms(int milliseconds) {
	int sleep_until = *timer_value_register + milliseconds;
	while (*timer_value_register < sleep_until) {}
}

// This function is the entry point for our application and the handler
// function for the reset interrupt.
void start() {
	// Enable PB27 (pin 13) and configure it for output.
	*pb_pio_enable    = pb27_mask;
	*pb_output_enable = pb27_mask;

	// Set the timer to a resolution of a millisecond.
	*timer_mode_register = 0x00000020;

	// Continuously set and clear output on PB27 (pin 13). This blinks
	// the Due's built-in LED, which is the single purpose of this
	// program.
	while (1) {
		*pb_set_output_data = pb27_mask;
		sleep_ms(200);
		*pb_clear_output_data = pb27_mask;
		sleep_ms(800);
	}
}
			

Figuring out the correct build command took a bit of time[2], but it didn't end up much different from before:

clang \
	-nostdlib \
	-ffreestanding \
	-target arm-none-eabi \
	-march=armv7-m \
	-Tlinker-script.ld \
	-Wl,--entry=start \
	blink.c \
	-o blink.elf
			

There are a few gotchas to look out for here:

The rest of the build and upload commands are unchanged:

arm-none-eabi-objcopy \
	-O binary \
	blink.elf \
	blink.bin

bossac --write --verify --boot -R blink.bin
			

Please don't forget to press the tiny ERASE button on the Arduino Due before attempting to upload the program with bossac.

That's it for today. As always, the full code is available on GitHub. See you next time!