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.
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).
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.
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.
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$
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.
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.
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.