By Jon Titus
When you start to write libraries, it takes time to read documents, follow directions, and experiment with simple functions. Fortunately, after you understand how to create libraries and header files for the software tools you use, you’ll have a useful skill that can simplify programming tasks.
If you have written a program for a microcontroller board such as the Arduino, Raspberry Pi, or Propeller QuickStart, you have relied on software libraries that provide constants and functions. Often, we use software libraries without thinking much about them.
In this tutorial, I’ll explain how software uses libraries and how you can create your own.
Each software development tool has its own way to create libraries. For the examples that follow, I’ll use the Parallax SimpleIDE for the Propeller microcontroller (MCU). Several reasons explain why we use libraries:
To be fair, we can find several reasons to not use a library:
Even if you never create a library, you probably want to know how they work and what they contain. This tutorial provides information that gives you a good start.
Before we discuss library details, you should understand how programs go from text files to the machine language (1s and 0s) that tells an MCU what to do. When we key a program into a text window and save it as, say, SimpleTest.c, we have created source code. When we’re ready, our software development “tools” pass SimpleTest.c through a preprocessor that executes directives such as #include.., #define.., and #endif.
The “preprocessed” information next goes to a compiler that converts the software into an object file, so now we would have the file SimpleTest.o. The suffix indicates object code. However, the compiler doesn’t have all the information it requires to give us ready-to-run machine code. It still needs information from library files such as stdio.c or servo.c.
The header file gives the compiler spaces for the linker to fill with the library function call, arguments, and returned value so a precompiled function can be linked.
The final step sends all the object files for SimpleTest, stdio, servo, and others to a linker that combines all object files seamlessly. Lastly, we get an executable machine language file for our MCU. Our executable file often goes through a loader that translates the addresses from the linker into the addresses required by a specific MCU’s memory map and register arrangement.
In many cases, we use precompiled library files to save time. Then, our software tools don’t need to compile large libraries each time we make a change to SimpleTest.c.
Precompiled object libraries have a dot-o extension. Precompiled functions also offer security because users do not see the source code. That becomes important for companies that aim to keep their intellectual property confidential.
In general, we create static libraries for MCUs that our compiler incorporates into our application program. These static libraries have a dot-a (archive) extension. Operating systems such as Windows and Linux provide static and dynamic libraries. The operating system links dynamic libraries to programs at run time, so several applications that run simultaneously share a single dynamic library. In Linux, for example, you would see dynamic library files with a dot-so (shared object) extension. (File extensions may vary by compiler type.)
Luckily for us, when we click on an IDE (integrated development environment) menu item such as “Build,” “Build Project,” or “Verify/Compile,” the IDE’s preprocessor, compiler, and linker work their magic and automatically combine the needed object files. The IDE might also let you immediately load the code into your MCU so you can test it.
For various reasons, some programmers take a command-line approach to compiling and linking code files. For more information, refer to the Resources.
The IDE you use (say, Microchip MPLAB, Parallax SimpleIDE, mbed, or Arduino) must include standard libraries specified in the C language standard. Commonly used libraries offer math, string, time, input-output, and other functions.
When you look at a program listing, you’ll see precompiler directives at the top that indicate the library (or libraries) the program needs. The necessary #include statements supply a library file name followed by a dot-h extension. We call the dot-h files header files.
Here’s a simple program I created with the Parallax SimpleIDE to flash LEDs on a QuickStart board. The first statement indicates my code needs the standard Propeller library Simpletools.h:
/*
Blink_Lights.c
Blink all LEDs on QuickStart board five times
*/
#include “simpletools.h” // Include simpletools
void LED_Test (void)
{
int n;
int cycle_count;
cycle_count = 5;
while(cycle_count > 0) // Loop 5 times
{
for (n = 16; n<24; n++)
{
high(n); // Set I/O pin n high
pause(100); // Wait 0.20 seconds
low(n); // Set I/O pin n low
pause(100); // Wait another 0.02 seconds
}
cycle_count--; // decrement loop counter
}
}
int main() // main code
{
LED_Test();
}
// End of Blink_Lights.c
The functions high, low, and pause do not occur in the C language. They get declared and defined in the simpletools.h file or in its subfiles. Parallax documents clearly explain the functions and constants available, so I use them without a second thought. I don’t need to know how the high, low, and pause functions work, although I could investigate them if I chose to. (Caution: Some IDEs use complicated “trees” to arrange library files, so a search through them might seem like a trip down a rabbit hole.)
The names of header files appear at the start of a program in two formats:
#include <stdio.h>
#include “servo.h”
A header file name between carets (#include <stdio.h>) causes the preprocessor to search for the named file within directories and folders automatically created when you install an IDE. Here, you’ll find the libraries required by the C and C++ standards. (Read the IDE supplier’s documents that explain how to use its libraries before you write code.)
The preprocessor treats the file name enclosed in quotes in a different way. According to the C17 draft standard for the C language, the preprocessor replaces the #include “servo.h” directive with the entire contents of the named header file. If the preprocessor can’t find the file — usually it’s in your working directory or folder — it treats the directive as if you had written #include <servo.h>. (See Reference 1.)
In addition to the libraries supplied within an IDE, you can find other libraries written by users who either improved on an IDE’s library, added functions to a library, or created a library for a specific peripheral device.
When you open the SimpleIDE header file servo.h, you’ll find many function prototypes such as those shown next. Each defines the data type a function will expect from your software and the data type of returned information, if any. The servo_stop() function, for example, requires no data and it returns no data:
int servo_set(int pin, int time);
int servo_get(int pin);
void servo_stop(void);
int servo_setramp(int pin, int stepSize);
The statement #include “servo.h” causes the preprocessor to replace the servo.h directive with the entire contents of the servo.h file. Remember, the dot-h files contain only function prototypes, not the functions themselves. The prototypes “promise” the compiler that the functions exist elsewhere (in a library file), and specify how much memory the functions need and the variables involved. As a result, all the servo functions and other information in servo.h become available to your program. Whoever provided the servo.h and the accompanying servo.c files should have documented what the functions do and how you can use them.
You can find the C/C++ standard library application programming interface (API) specifications either in the language standard or on Internet sites such as https://en.wikipedia.org/wiki/C_standard_library which has links to specific library pages, and www.cplusplus.com/reference/clibrary which has a helpful index to the libraries.
For the Propeller servo example, those function definitions — the code that does the work — appear in the servo.c file. The servo_stop function looks like this:
void servo_stop(void)
{
if(servoCog - 1);
{
servoCog = 0;
lockclr(lockID);
lockret(lockID);
}
}
The library creator declared servoCog and other variables elsewhere. Again, assume the libraries provided as part of a company’s IDE went through many tests and work properly. If you do find a problem, the company will welcome your error report.
Parallax provides library creation instructions for the Propeller that explain how to use the SimpleIDE to create them. I needed an LED_Test library that flashed LEDs on a QuickStart board. Due to confusion about file names in the three-part instructions, it took several attempts to get the library properly set up. However, I now know the process and have suggested revisions to the Parallax lessons. I’ve provided my instructions and example software in a file available with the article downloads.
For information about how to create libraries for various MCUs, please refer to the Resources section of this article. Based on my experience, I recommend you carefully read the instructions — perhaps several times — before you start any library-related work.
Also, the software you want to put in a library should run properly before you put it in a library.
The SimpleIDE library creation process starts after you create folders to hold project and library files. You then put files in folders and do minor editing. Finally, you build the project; that step creates the proper library files.
The LED_Test library worked and turned each LED on and off in sequence five times. The main program file now looks like this:
#include “simpletools.h”
#include “LED_Test.h”
int main()
{
LED_Test();
}
Propeller libraries use one of three memory modes: Large (LMM); Compact (CMM); and Cog RAM (COG). I used the default CMM setting when I created the LED_Test library. If you examine Propeller library files, you’ll see folders for appropriate memory models. The servo library, for example, includes CMM- and LMM-type libraries.
The SimpleIDE window includes an icon in the bottom-left corner of the main project area. I clicked on it to open a Project Manager area that shows three tabs: Project Options; Compiler; and Linker. The SimpleIDE user guide explains the memory modes, speeds of execution, and memory sizes, so you can choose one appropriate for your application. Experiment and try more than one model.
When programmers create libraries, they should always add include guard code that prevents library users from invoking a library more than once. Multiple inclusions of a library cause compile-time errors because your program has tried to declare variables and functions more than once. An include guard requires two statements at the start of a library file and one at the end. The following example file illustrates how to place an include guard in a library file prior to compiling it:
//Start of servo.h file
#ifndef servo_H //Library starts here
#define servo_H
all servo.h library code goes here...
#endif //Library ends here
The guard directives work this way: The first time the preprocessor reaches the directive #ifndef servo_H, it finds nothing has yet defined servo_H. So, the next directive defines servo_H. (Think of a flag bit that could be set or not set. The servo_H works the same way; either it’s defined or it’s not defined.) After servo_H gets defined here, the preprocessor continues through the code until it reaches the library’s #endif directive. Then, it copies the servo.h library into your application program. All code in a header file belongs between the #define and the #endif directives. After the preprocessor finishes with the servo.h file, servo_H remains defined.
If you try to use the servo.h file again elsewhere in your program, the preprocessor now finds servo_H already defined, so it jumps to the #endif directive and does not re-use any information in the servo.h file. So, although you might inadvertently have several #include “servo.h” statements in a program (yes, it does happen), the servo library gets included only once. Of course, you may use the functions in a library as many times as you wish in a program.
The C++ language allows for function overloading, which means a function name may have several meanings: void test(int); or void test(int, char); or void test(void); for example. When run through a C++ compiler, the function names get “mangled,” so they identify themselves uniquely.
The mangled name indicates the data type (or types) associated with the function name. Mangled names affect the way a linker connects pieces of code to create an executable file. The section that follow shows how the three test function names appear after being mangled by a C++ compiler (gcc 3.x and higher):
void test(int); Z1testi
void test(int, char); Z1testic
void test(void); Z1testv
Within a C library file, we must ensure the function declarations don’t get mangled and that we preserve “C linkage.” The process that links sections of code to create an executable program cannot match a mangled C++ function name with one un-mangled by a C compiler.
To prevent name mangling and linker errors, we direct a compiler to treat sections of code as written in C, rather than in C++. This protection lets linkers use our library in C code without problems. A detailed description of function overloading and name mangling goes beyond the scope of this tutorial (see Reference 2 for more details). To ensure names in a C language header file do not get mangled, you must add six more preprocessor directives (shown here in bold and numbered for discussion):
//Start of servo.h file
#ifndef servo_H //Library starts here
#define servo_H
#ifdef __cplusplus //#1
extern “C” { //#2
#endif //#3
all servo.h library code goes here...
#ifdef __cplusplus //#4
} //#5
#endif //#6
#endif //Library ends here
The first new directive (#1) tests for a declaration of __cplusplus, which gets defined automatically by a C++ compiler. So, in this example, assume it is defined. The statement extern “c” { (#2) indicates, “OK, __cplusplus is defined here, so from this point on the left brace -- { -- the compiler will not mangle names.
We can use them as written in a C or C++ program. The #endif directive (#3) ends this group of three directives. (You can’t apply extern to code written specifically for C++.)
The final #ifdef __cplusplus directive (#4) again tests __cplusplus which, in this case, remains defined. So, the right brace (#5) ends the no-mangle section of the library’s C code. The final #endif directive (#6) ends this group of three directives. The include guard and the prevention of name mangling can help you avoid errors that might challenge your debugging skills.
You now have had an introduction to how libraries get created, how to protect library code from a C++ compilation, and how to guard against multiple library-inclusion attempts. As your next step, learn more about how your IDE creates libraries and try to create a simple one. NV
1. ISO/IEC 9899:2017, “C17 Ballot,” Section 6.10.2, “Source file inclusion.”
2. “Name Mangling,” Wikipedia, https://en.wikipedia.org/wiki/Name_mangling.
3. Drysdale, David, “Beginner’s Guide to Linkers,” https://www.lurklurk.org/linkers/linkers.html.
To download the improved instructions needed to create a Propeller library with the Parallax SimpleIDE, go to this article’s downloads.
For more information about the gnu compiler collection and the compilation and linking processes, please refer to, “An Introduction to GCC for the GNU Compilers gcc and g++,” by Brian Gough, Network Theory Limited, Bristol, UK. 2005. ISBN: 9780954161798.
Parallax SimpleIDE User Guide, https://learn.parallax.com/sites/default/files/content/propeller-c-reference/landing/SimpleIDE-User-Guide-9-26-2.pdf.
“Propeller C Library Studies,” a multipart series about libraries and how to create them at https://learn.parallax.com/tutorials/language/propeller-c/propeller-c-library-studies.
“How to Compile and Run a C Program on Ubuntu Linux,” Keld Helsgaun, http://webhotel4.ruc.dk/~keld/teaching/CAN_e14/Readings/How to Compile and Run a C Program on Ubuntu Linux.pdf.
“Writing an MBED Library,” https://os.mbed.com/cookbook/Writing-a-Library.
“Starting a New GCC Static Library Project,” Atmel Studio 7 User Guide, Section 3.2.6, http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-42167-Atmel-Studio_User%20Guide.pdf.
“Libraries,” http://microchipdeveloper.com/mplabx:libraries. Microchip has an excellent description of how programs go from C/C++ through a compiler and linker, and how you can create a library.
Thanks go to my friends, Dr. Michael Batchelder and Mr. Len Bayles whose suggestions improved this article.