One of the great features of the Arduino community is the availability of thousands of pre-written libraries that add hardware and other functionality to your projects without needing to write your own code. There are usually quite a few to pick from and your code will often depend on libraries, so the quality of the library you use can be critical to how your code performs. How do you write good libraries and how would you evaluate the quality of the latest library you downloaded?
What is an Arduino library?
In the Arduino environment, a library is a collection of code that makes it easy for you to connect to a sensor, display, or other module – for example LiquidCrystal library makes it easy to use character LCD displays, the SPI library to use the SPI interface.
Physically, the library is a folder containing files to provide your source code with functionality you would otherwise have to write yourself. This functionality is usually encapsulated as one or more C++ classes (the C++ code files will end in .cpp and header files in .h) and some optional files to provide metadata support for the Arduino IDE. All files are distributed as source code (ie, there are no object or other binary files).
In fact, most of the Arduino specific functions are libraries provided to allow microcontroller functionality built on top of, or as alternatives to, the standard C++ language libraries. The “Arduino Programming Language” is just C++ with libraries.
Elements and structure of an Arduino library
In this discussion we will consider libraries meant for the Arduino IDE version 1.5 or greater. Prior to this libraries were structured differently.
The location and folder structure for libraries is shown on the left. User libraries are located in a libraries subfolder of the Arduino sketchbook folder. This ensures that user libraries are maintained intact following an IDE upgrade installation or full re-installation.
The library myLibrary is contained in its own subfolder. The source code for the library (header and code files) is located in the src subfolder of myLibrary. By convention, the header file that is included in your source code should be called myLibrary.h to match the library name. Any additional header files and the source code files can be named anything you want, as they are all included in the IDE compile cycle. It is, however, good practice to adopt a naming convention that will identify these files as part of the same library set.
The myLibrary folder also contains additional optional folders:
- The examples folder contains examples that illustrate the functionality of the library. These examples will be included in the IDE File|Examples menu list as read only sketches.
- The Library Specification suggests that the extras folder is for “documentation or other items to be bundled with the library”, and is ignored by the IDE.
- A docs folder is a preferred folder for library documentation, especially if the library is published via GitHub, as html documentation can be made available online through this folder.
Two special files are also included in the myLibrary root folder. The format for both can be found in the Library Specification.
- The keywords.txt file provides the IDE with a list of keywords it should highlight in different colors. If the file is omitted, the only consequence is that the IDE does not highlight keywords.
- The library.properties file provides identification metadata (such as version number, descriptions) for the IDE to manage the library. This file allows the IDE Library Manager to search for, and install, a library and its dependencies in an easy and automated way. Although optional, it is highly recommended that this file be included and maintained as part of the code set.
After having written dozens of libraries that are used by thousands of people, I have developed a set of practices that make developing, documenting and deploying libraries more manageable and supportable for end users.
Make sure your library works, and if it doesn’t, clearly state so. There is nothing more frustrating than downloading a library only to find that even the examples provided don’t work. Always test your examples against any library updates to make sure that everything still works. Seems obvious, but …
Document your library, but don’t expect that users will actually read the documentation. This can be as simple as a basic readme file that clearly states what the library does, which boards the library was designed for, on which it was tested, and what boards is it expected to work. It pays to develop and maintain the documentation in parallel with the code – tools like Doxygen do a great job of taking formatted comments from your source code and creating final docs at any stage of the process. While this may seem like a waste of time, one major element of end user quality is how easy the library is to deploy and use.
Be as clean and as efficient as possible. Your library is a black box to others – most will never look at it – so your code needs to work 100% of the time and be as efficient as you can make it. Avoid the use of busy waits and the delay() function. Check your function parameters and size your variables appropriately so as not to waste memory. Make sure that any memory allocated in the constructor or begin() is freed in the destructor. Especially for embedded programming, strive to write code that will never fail catastrophically. Defensive programming is the key here.
Don’t hard code anything unless it is absolutely necessary. Array sizes, pin numbers, names, etc, should be set up as variables. Don’t use magic numbers for constants, etc – either #define them or declare constants and make the names clear. This provides the user flexibility in how they deploy the library and is easily done while the library is developed – much harder to retro-fit after the fact. Any hard coded limits should be clearly documented so that users know what they are.
Provide sensible defaults for configurable items so that, in most situations, users do not have to change them. Provide methods to change these even if you expect them to be seldom used.
Write great examples. The Arduino community includes a disproportionate number of beginners and inexperienced users, so the closer an example is to what the user is looking for the more likely they are to use the library successfully. Ensure that you include simple examples as well as more complex ones. Most Arduino users will ‘load and go’, so examples must be well commented and any dependencies on other libraries (your own or others) must be documented in the .ino file, including the location where they can be obtained.
Support multiple platforms by leveraging the Arduino hardware functions that are abstracted for different architectures. Avoid the use of assembler and other architecture specific features as these create support and maintenance issues, and limit the usability of the library across the broader Arduino community.
Create good interface methods. A working library with convoluted interfaces is frustrating, so keep them as simple as possible. Generally you should consider functionality broadly split into initialisation, open, close, read, write, device control, library control (or options). Also consider combining often-used call combinations into a single method, as this makes it easier for beginners. My preference is functions with generalised parameters, rather than specific functions, as it allows a data driven approach – for example, a method deviceControl(controlType_t whatControl) rather than separate methods such deviceRunOn(), deviceRunOff(), deviceSuspend(), etc. This differs from the Library Specifications, but the implicit style of invocation can be written as wrapper stubs using the more generalised methods.
Always include a begin() method. You may not need it now, but this allows for future initialisation steps without breaking the body of existing user code, causing frustration, rewrites and support headaches.
Comments are critical for maintaining code. Make sure to comment every change you make. Write nice long comments for critical functions and shorter ones for others. Explicitly describe your interface, each argument, what it does, and what it returns in a structured, consistent, manner. This may be a lot of extra work, but will pay off if you also use automated tools to generate documentation from your code (see the section on Documentation above).
Consistency. Make sure everything (coding style, comments, structure, formatting ) are consistent across the .h and .cpp files. Keep related functions a single file – a few small, but logically consistent, modules is better than one monolithic file.
Be sure to add a License, Version and Copyright information. Version information is mandatory requirement if you plan on making the library available through the Library Manager IDE interface, and you may as well make it clear the basis on which you are making the code available to the user community.
Publishing your library
Once you have created you library you may wish to publish it to the world.
One option is to store the source code on a publicly accessible code repository (like GitHub, BitBucket or GitLab) and hope people will find it. They can then download and install it manually on their systems.
A better option is to use the process the Arduino Team has created to allow your library to be found by the Library Manager:
- Host the library on a major git-hosting website. GitHub is the easiest here as Arduino is also on GitHub.
- Ensure your library is compliant with the version 1.5 format (discussed above) as the v1.5 format folder layout is required.
- Tag the code and push the tag (or create a release) of your code. The tag must be the version number in 1.0.0 format and match the version number in the library.properties file.
- On the Arduino GitHub site, open an issue specifying the URL of the repository you wish to add to the Library Manager list.
- When your issue is dealt with and closed, the library will be available for installation via Library Manager. Subsequent updates of the repository will automatically be picked up provided the version number in the Tag and library.properties match.
“Arduino IDE 1.5: Library Specification”, Arduino github site at https://github.com/arduino/arduino/wiki/arduino-ide-1.5:-library-specification
“Arduino Library Style Guide“, Arduino References site at http://arduino.cc/en/Reference/APIStyleGuide