Chapter 6. Libraries

Libraries provide the means to package reusable pieces of Arduino Software. You can create them yourself, or find libraries on the Internet. You can leave them as is, or (subject to licenses) modify them to suit your own purposes.

Using libraries with dno is about as easy as it gets. Dno:

All of this means that dno can be used to build professional-quality, well-tested, and well-documented code, which you can be proud of sharing with the world.

6.1. Developing With Libraries - A Walkthrough

6.1.1. Setting Up

In Getting Started, we created a blink project, and created the sketch blink.ino.

Then in The BOARD_TYPE File, And Board Types section we suggested that using board-specific subdirectories was the preferred way to use dno.

So, after running dno pristine, we have a directory system that looks like this:

blink$ ls -l
total 8
-rw-r--r-- 1 user user 1233 Jan  6 11:04 blink.ino
drwxr-xr-x 2 user user 4096 Jan 12 11:30 pro.8MHzatmega328
blink$ ls -l pro.8MHzatmega328/
total 0
blink$
	

That is, it contains our sketch file blink.ino and an empty board directory pro.8MHzatmega328 (choose a board type that works for you).

6.1.2. Wanting A Better Blink

Our blink.ino sketch is a little too simple. One of its problems is that the blink timing is handled by busy-wait delays. This means that nothing else can be done while waiting to turn the LED on or off. It also means that there will be some timing drift as each timeout starts a few cycles after the previous one. It would be good to remedy these problems.

What we need is a non-blocking timer of some sort. Let's take a look at the Deferal library.

Yep, looks promising.

6.1.3. Installing the Deferal Library

On the github page for Deferal, there is a Code button. Pressing this (in today's browser at least) gives us the url for a git clone operation.

Let's move to our project root directory and clone the library:

blink$ git clone https://github.com/marcmunro/Deferal.git
Cloning into 'Deferal'...
remote: Enumerating objects: 26, done.
remote: Counting objects: 100% (26/26), done.
remote: Compressing objects: 100% (20/20), done.
remote: Total 26 (delta 5), reused 23 (delta 5), pack-reused 0 (from 0)
Receiving objects: 100% (26/26), 54.05 KiB | 643.00 KiB/s, done.
Resolving deltas: 100% (5/5), done.
blink$
	

Taking a look at the new library:

	  cd Deferal/
Deferal$ ls
Deferal.cpp  Deferal.h	docs  tests
Deferal$ 
	

It looks like it comes with tests and documentation. Let's see:

Deferal$ dno
make[1]: Entering directory '.../blink/Deferal/tests'
  C++ [.] test_Deferal.cpp
  C++ [Deferal] Deferal.cpp
  LD [test_Deferal]
  RUN test_Deferal...
    ...................................................................................
    --------------------------------------------------
    Ran 83 checks in 0.001188s
    
    OK
  ...DONE (test_Deferal)

make[1]: Leaving directory '.../Deferal/tests'
:Deferal$ 
	  
	

So, it comes with unit tests that dno can run, and they appear to pass. We should assume that's good.

What about the docs?

Deferal$ dno docs
  DOXYGEN 
Doxygen version used: 1.9.1
Searching for include files...
Searching for example files...
Searching for images...
Searching for dot files...
Searching for msc files...
Searching for dia files...
Searching for files to exclude
Searching for files in directory .../blink/Deferal/tests
Searching for files in directory .../blink/Deferal/tests/build
Searching for files in directory .../blink/Deferal/tests/build/Deferal
Searching INPUT for files to process...
Searching for files in directory .../blink/Deferal
Searching for files in directory .../blink/Deferal/docs
Searching for files in directory .../blink/Deferal/html
Searching for files in directory .../blink/Deferal/tests
Searching for files in directory .../blink/Deferal/tests/build
Searching for files in directory .../blink/Deferal/tests/build/Deferal
Reading and parsing tag files
Parsing files
Preprocessing .../blink/Deferal/Deferal.cpp...
Parsing file .../blink/Deferal/Deferal.cpp...
Preprocessing .../blink/Deferal/Deferal.h...
Parsing file .../blink/Deferal/Deferal.h...
Reading .../blink/Deferal/docs/README.md...
Building macro definition list...
Building group list...
Building directory list...
Building namespace list...
Building file list...
Building class list...
Computing nesting relations for classes...
Associating documentation with classes...
Building example list...
Searching for enumerations...
Searching for documented typedefs...
Searching for members imported via using declarations...
Searching for included using directives...
Searching for documented variables...
Building interface member list...
Building member list...
Searching for friends...
Searching for documented defines...
Computing class inheritance relations...
Computing class usage relations...
Flushing cached template relations that have become invalid...
Computing class relations...
Add enum values to enums...
Searching for member function documentation...
Creating members for template instances...
Building page list...
Search for main page...
Computing page relations...
Determining the scope of groups...
Sorting lists...
Determining which enums are documented
Computing member relations...
Building full member lists recursively...
Adding members to member groups.
Computing member references...
Inheriting documentation...
Generating disk names...
Adding source references...
Adding xrefitems...
Sorting member lists...
Setting anonymous enum type...
Generating citations page...
Counting members...
Counting data structures...
Resolving user defined references...
Finding anchors and sections in the documentation...
Transferring function references...
Combining using relations...
Adding members to index pages...
Correcting members for VHDL...
Computing tooltip texts...
Generating style sheet...
Generating search indices...
Generating example documentation...
Generating file sources...
Generating code for file Deferal.cpp...
Generating code for file Deferal.h...
Parsing code for file docs/README.md...
Generating file documentation...
Generating docs for file Deferal.cpp...
Generating docs for file Deferal.h...
Generating docs for file docs/README.md...
Generating page documentation...
Generating group documentation...
Generating class documentation...
Generating docs for compound Deferal...
Generating call graph for function Deferal::Deferal
Generating call graph for function Deferal::Deferal
Generating call graph for function Deferal::~Deferal
Generating call graph for function Deferal::again
Generating call graph for function Deferal::checkDeferals
Generating call graph for function Deferal::init
Generating call graph for function Deferal::pause
Generating call graph for function Deferal::paused
Generating call graph for function Deferal::running
Generating call graph for function Deferal::start
Generating call graph for function Deferal::status
Generating call graph for function Deferal::stop
Generating call graph for function Deferal::stopped
Generating call graph for function Deferal::updateStatus
Generating namespace index...
Generating graph info page...
Generating directory documentation...
Generating index page...
Generating page index...
Generating module index...
Generating namespace index...
Generating namespace member index...
Generating annotated compound index...
Generating alphabetical compound index...
Generating hierarchical class index...
Generating member index...
Generating file index...
Generating file member index...
Generating example index...
finalizing index lists...
writing tag file...
Running plantuml with JAVA...
Running dot...
Generating dot graphs using 5 parallel threads...
Running dot for graph 1/18
Running dot for graph 2/18
Running dot for graph 3/18
Running dot for graph 4/18
Running dot for graph 5/18
Running dot for graph 6/18
Running dot for graph 7/18
Running dot for graph 8/18
Running dot for graph 9/18
Running dot for graph 10/18
Running dot for graph 11/18
Running dot for graph 12/18
Running dot for graph 13/18
Running dot for graph 14/18
Running dot for graph 15/18
Running dot for graph 16/18
Running dot for graph 17/18
Running dot for graph 18/18
Patching output file 1/3
Patching output file 2/3
Patching output file 3/3
lookup cache used 67/65536 hits=651 misses=68
finished...
Deferal$ 
	

We can view the generated documentation by viewing the docs/html/index.html file in a browser.

6.1.4. Updating Our Sketch In Preparation To Use The Library

Before we change the sketch to use Deferal objects, we'll refactor it. This is to make it cleaner, easier to work with, and easier to subsequently change.

Some of this is cosmetic and down to personal preferences but whatever your preferences are (or your coding standards require) it is better to make these changes now than being forced into a coding style you dislike, or worse still, ending up with conflicting styles throughout the code.

We'll start by removing most of the comments, which are currently not that helpful, and re-formatting to the author's preferred style. Then we'll build again:

pro.8MHzatmega328$ [edit edit edit]
pro.8MHzatmega328$ cat ../blink.ino
/*
  Blink

*/

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
    digitalWrite(LED_BUILTIN, HIGH);
    delay(1000);
    digitalWrite(LED_BUILTIN, LOW);
    delay(1000);
}
	  
pro.8MHzatmega328$ dno
  C++  [..] blink.ino
  AS  [core] wiring_pulse.S
  C  [core] wiring_shift.c
  C  [core] wiring_pulse.c
  C  [core] wiring_digital.c
  C  [core] wiring.c
  C  [core] wiring_analog.c
  C  [core] WInterrupts.c
  C  [core] hooks.c
  C++  [core] WString.cpp
  C++  [core] WMath.cpp
  C++  [core] USBCore.cpp
  C++  [core] Tone.cpp
  C++  [core] Stream.cpp
  C++  [core] Print.cpp
  C++  [core] PluggableUSB.cpp
  C++  [core] new.cpp
  C++  [core] main.cpp
  C++  [core] IPAddress.cpp
  C++  [core] HardwareSerial.cpp
  C++  [core] HardwareSerial3.cpp
  C++  [core] HardwareSerial2.cpp
  C++  [core] HardwareSerial1.cpp
  C++  [core] HardwareSerial0.cpp
  C++  [core] CDC.cpp
  C++  [core] abi.cpp
  AR [libcore] abi.o...
  LD blink.ino.o
  OBJCOPY (hex) blink.elf
pro.8MHzatmega328$ 
	

Note that we don't appear to have compiled the Deferal library. This is correct, since our sketch does not currently make use of it.

Now, we'll create some functions for turning the led on and off and toggling it. We'll also change the timing, so that we can see whether the new code is running when we attempt to upload it:

pro.8MHzatmega328$ [edit edit edit]
pro.8MHzatmega328$ cat ../blink.ino
/*
  Blink

*/

static bool led_is_on;

static void
led_on(int led)
{
    digitalWrite(led, HIGH);
    led_is_on = true;
}

static void
led_off(int led)
{
    digitalWrite(led, LOW);
    led_is_on = false;
}

static void
toggle_led(int led)
{
    if (led_is_on) {
	led_off(led);
    }
    else {
	led_on(led);
    }
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    led_off(LED_BUILTIN);
}

void loop()
{
    led_on(LED_BUILTIN);
    delay(800);
    led_off(LED_BUILTIN);
    delay(200);
}
pro.8MHzatmega328$ dno
  C++  [..] blink.ino
  LD blink.ino.o
  OBJCOPY (hex) blink.elf
pro.8MHzatmega328$ 
	

Upload it and see if the blinking has changed:

pro.8MHzatmega328$ dno upload
  Resetting device attached to (/dev/ttyUSB0)...
/usr/local/bin/dno do_upload
make[1]: Entering directory '.../blink/pro.8MHzatmega328'
 
    Uploading blink.hex to /dev/ttyUSB0

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: reading input file "build/blink.hex"
avrdude: writing flash (952 bytes):

Writing | ################################################## | 100% 0.51s

avrdude: 952 bytes of flash written
avrdude: verifying flash memory against build/blink.hex:
avrdude: load data flash data from input file build/blink.hex:
avrdude: input file build/blink.hex contains 952 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.43s

avrdude: verifying ...
avrdude: 952 bytes of flash verified

avrdude: safemode: Fuses OK (E:00, H:00, L:00)

avrdude done.  Thank you.

make[1]: Leaving directory '.../blink/pro.8MHzatmega328'
pro.8MHzatmega328$
	

6.1.5. Updating Our Sketch To Use The Library

The first thing we do is to add an include directive for the library.

#include <Deferal.h>
        

Then we'll run dno again:

pro.8MHzatmega328$ dno
  C++  [..] blink.ino
  C++  [Deferal] Deferal.cpp
  LD blink.ino.o Deferal.o
  OBJCOPY (hex) blink.elf
pro.8MHzatmega328$ 
        

We see that our sketch has been recompiled (because we've updated it), and also that the Deferal library got compiled. This means that dno has automatically discovered that the library is now needed[3]. We haven't had to tell it to use the library, or where to find it. We have just placed the library into our project directory, and starting using it in the code.

So, now we need to start using Deferal objects.

pro.8MHzatmega328$ [edit edit edit]
pro.8MHzatmega328$ cat ../blink.ino
/*
  Blink

*/

#include <Deferal.h>

static bool led_is_on;
static Deferal myTimer(500);

static void
led_on(int led)
{
    digitalWrite(led, HIGH);
    led_is_on = true;
}

static void
led_off(int led)
{
    digitalWrite(led, LOW);
    led_is_on = false;
}

static void
toggle_led(int led)
{
    if (led_is_on) {
	led_off(led);
    }
    else {
	led_on(led);
    }
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    led_off(LED_BUILTIN);
}

void loop()
{
    if (!myTimer.running()) {
	toggle_led(LED_BUILTIN);
	myTimer.again();
    }
    // We can add code to do all sorts of things here, without the
    // timing of our led blinks being affected.
    
}
pro.8MHzatmega328$ dno
  C++  [..] blink.ino
  LD blink.ino.o Deferal.o
  OBJCOPY (hex) blink.elf
pro.8MHzatmega328$ dno upload
  Resetting device attached to (/dev/ttyUSB0)...
/usr/local/bin/dno do_upload
make[1]: Entering directory '.../blink/pro.8MHzatmega328'
 
    Uploading blink.hex to /dev/ttyUSB0

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: reading input file "build/blink.hex"
avrdude: writing flash (1478 bytes):

Writing | ################################################## | 100% 0.77s

avrdude: 1478 bytes of flash written
avrdude: verifying flash memory against build/blink.hex:
avrdude: load data flash data from input file build/blink.hex:
avrdude: input file build/blink.hex contains 1478 bytes
avrdude: reading on-chip flash data:

Reading | ################################################## | 100% 0.65s

avrdude: verifying ...
avrdude: 1478 bytes of flash verified

avrdude: safemode: Fuses OK (E:00, H:00, L:00)

avrdude done.  Thank you.

make[1]: Leaving directory '.../blink/pro.8MHzatmega328'
pro.8MHzatmega328$ 
        

So, no more busy-waits. Our Arduino can now do lots of other stuff and still keep the led flashing at a steady rate.

6.2. Unit Testing

Testing Arduino code can be difficult.

Generally you write the code, upload it, and see if it does what you expect. If it doesn't then you have to reason about it, or maybe add some serial writes so that you can see what bits of code are actually being executed.

If you have code paths that you don't expect to normally be executed, testing them can be very difficult indeed. Normally you have to change your code to make the rare case happen, try that modified code out, and then change the code back again. Which means that you are not testing the code that will actually run, and if you accidentally change something in that code path it may never get tested again.

The solution to this is to put as much code as possible into libraries as these can be unit-tested on your host computer. If the library's unit tests are well designed, you should be able to test all code paths, no matter how unlikely to be triggered. As a bonus, the tests also become automated and repeatable.

6.2.1. Unit Testing With Dno

When run in a library or its tests subdirectory, dno will attempt to create a file called test_<LIBNAME>. This will be created by compiling the file test_<LIBNAME>.cpp or test_<LIBNAME>.c. Having created the executable, it will then run it with no parameters. It is assumed that if the executable completes with a success code, that the tests have passed.

Dno does not itself provide a unit-testing framework. Instead, any such framework needs to be included in the library's tests subdirectory.

One such suitable framework is CPlusPlusUnit, which is very small and simple but provides just about all of the functionality you might need, though the documentation is a little sparse. Check the Deferal library for examples of usage.



[3] This would be a good time to be impressed.