Hello World Application


This page serves as a very basic tutorial (probably one of the first you should read) presenting how to build an application using the Dooba SDK.

In first part, we'll discuss the main aspects of structuring an application for building with the SDK.
Then, we will cover the basics of digital I/O and working with microcontroller code.
Finally, we'll see how to build and flash the application onto a target device.

Hello, World!

The application we will build is a simple classic hello world application, which, in the uC world, usually translates to blinking a LED.

We will build this application for the ioNode board, which is an ATMEGA1284P running @ 10MHz.

This is basically all our application will do:

  1. Turn LED on
  2. Wait half a second
  3. Turn LED off
  4. Wait half a second
  5. Repeat

Before we start

The first thing we need to know when starting a new project is how we're using the SDK.
As explained in the setup instructions, there are two main ways to use the SDK:

We will not focus on these aspects in this example. It's really up to you whether you want to code directly inside the SDK or in a separate workspace.
Regardless of how you're using the SDK, making an application is always the same process.

Directory structure

Whether we're directly inside the SDK, or in a separate workspace, we need to create a folder for our application.
We'll call this folder hello_world.

mkdir hello_world

Let's go ahead and also create a sub-folder to hold our application's source code and resources.

mkdir hello_world/src

Application definition

We still have one tiny thing to do before we start actually writing our application: define it.

Create a file named dfe.conf inside your application's folder.

It needs to define the application's name (and the fact it's an application, not a library), and the desired target mcu & frequency.

# Hello World Application

# Name
name: hello_world

# Type - Application
type: app

# Target - ioNode (ATMEGA1284P @ 10MHz)
mmcu: atmega1284p
freq: 10000000L

The code

This is where you'll be having fun :)
For this example, we want a simple infinite loop, in which we just turn the LED on and off, with some delays in between.


Let's start with a simple skeleton: a main method in a main.c source file.

// This gives us access to I/O pins on any AVR device (ATMEGA1284P in our case)
#include <avr/io.h>

// This gives us access to various delay methods
#include <util/delay.h>

/* Just like any other C application, our entry point is 'main'
 * The only difference is that we don't take any arguments and return void
 * (actually, a firmware application should _NEVER_ return from main)
void main()
    // ToDo: do something

    // Ensure we never return

Actually doing something

Let's add the business logic to our 'main' method: blinking the LED.
Before we can use the digital I/O pin, we need to configure it as an output.

Each I/O port (A, B, C, ...) available on the microcontroller is managed using a set of associated registers.
Simply writing and reading those registers immediately affects the I/O pins.

In each of these registers, each bit corresponds to one pin: bit 0 is pin 0, bit 4 is pin 4.
The important registers are the following (replace 'x' with the port name - A, B, C, ...):

  • DDRx - Direction register (0 = input, 1 = output)
  • PORTx - Output state when configured as output (0 = low, 1 = high) / Internal pull-up when configured as input (0 = pull-up disabled / 1 = pull-up enabled)
  • PINx - Input state when configured as input (0 = low, 1 = high)

Knowing this, we can now complete our application:

// ...
void main()
    // Configure Port B4 as Output
    DDRB |= _BV(PORTB4);

    // Loop forever
        // Turn LED ON & Wait half a second
        PORTB |= _BV(PORTB4);

        // Turn LED OFF & Wait half a second
        PORTB &= ~(_BV(PORTB4));

Some notes about the code above:

  1. _BV(x) is a simple macro used to get a bitmask from a bit position. Basically all it does is: 1 << x.
  2. Before using the I/O pin connected to the LED (Port B4), we need to configure it as output. This is done by setting the corresponding bit in Port B's direction register: DDRB.

That's it! Our application is now complete and we can build it :)

However, let's not just stop there.
The SDK actually provides a library to control the ioNode's onboard LED.
So to make our code even simpler, let's refactor it to use that library.

Using libraries

To use a library, the first thing we need to do is add it to our application's definition.

So let's go back to our dfe.conf definition file and add a dependency.

# Hello World Application

# ...

# Dependencies
  - ionode

Ok, now we should be able to access anything provided by the ionode library.

Looking at the library's main header (src/ionode/src/ionode.h), we can see the following methods are exported:

// Initialize Board
extern void ionode_init();

// Flash Activity LED
extern void ionode_act_led(uint8_t s);

Let's use these to simplify our application's code.

// This gives us access to methods from the ionode library
// Note: we no longer need to include avr/io.h
#include <ionode/ionode.h>

// This gives us access to various delay methods
#include <util/delay.h>

// Application Entry Point
void main()
    // Initialize ioNode

    // Loop forever
        // Turn LED ON & Wait half a second

        // Turn LED OFF & Wait half a second

Much cleaner, isn't it?

Building & flashing

To build our application, simply run dbuild:
  • at the SDK root if you're working directly inside it
  • from your workspace root if you're in a separate one

There are many ways to run dbuild, the easiest being to just run it without any arguments.
Check out building with dbuild for more information.

The output (your firmware image) will be placed in out/bin/hello_world.hex.

The final step is actually putting that firmware onto a physical device. This operation is commonly referred to as flashing.

There are multiple ways to do this, and these usually depend on your target device.
You should refer to your microcontroller's specs / board's documentation for how to do this.

If, like us, you're using the ioNode, this step is really simple - check out the ioNode documentation for instructions.