How to create Arduino Library from Arduino Sketch

how to create arduino library

On previous article, I wrote an Arduino sketch for interfacing LCD 5110 display module with Arduino, it is not an Arduino library that can be distributed and share with others yet. In this article, I will explain how to create an Arduino library in 6 easy steps.

Part of this article and the library code are re-write and enhanced on Feb 2020 with additional information.

An Arduino sketch is a program often consists of a few user-defined functions together with two special functions that are core parts of every Arduino sketch: setup() and loop(). The setup() is called once when the sketch started, it is used to initialising the status of the program. The loop() function is where the main program logic and flow that utilises all the functions.

The sketch that we are going to use to convert to Arduino library is available here for download.

An Arduino library take an object-oriental programming approach by utilising C++ Class to separate the reusable functions from its application program, it provides a set of APIs (or interfaces) for programmers to use the library without knowing the detail implementation of the library.

Step 1 - Create a directory for your Arduino library

Step 1 of creating an Arduino library is to create a directory named after the functionality of the library within the Libraries directory of your Arduino Sketchbook directory. Since our library is related to LCD 5110 so we will name our library as LCD5110.

The Sketchbook directory and Library directory is where the Arduino IDE stores the sketches. The Library directory is automatically created by the IDE when Arduino IDE is installed. On Linux machines, the directory is called Sketchbook and it is typically located in /home/username/. On Windows and Macintosh machines, the default name of the directory is Arduino and is located in your Documents directory.

arduino-library-directory-structure
Arduino library directory structure

Step 2 - Add Arduino library header and source files

Step 2 is to create an Arduino library header file and source file. An Arduino library needs at least two files for a library: a header file with file extension of .h and the source file with extension of .cpp.

It is not mandatory but a good practice to put both .h and .cpp file into a src sub-directory with our LCD5110 library directory.

Go ahead to create two empty files named LCD5110.h and LCD5110.cpp under our newly created directory LCD5110/src/ now. You will need a programming text editor to do so as Arduino IDE editor does not works for .h and .cpp file extension. Once you created the file. If you launch your Arduino IDE and navigate to Sketch --> Include Library, you should see our library LCD5110 show up under the Contributed libraries section.

access-arduino-library-via-sketch-include-library menu
Access Arduino library via Sketch -> Include Library menu

Although the Arduino IDE recognised our newly created Arduino library now, but the LCD5110.h and LCD5110.cpp currently contains no functional code yet, so let's add codes into those files.

Setp 2.a - Arduino library header file (.h)

The header file provides the definitions of the library with a few preprocessor directives and a class prototype or class interface.

The #ifndef LCD5110_h ... #endif preprocessor directives basically avoid the file to be loaded multiple times.

The #include "Arduino.h" provides the access to the standard types and constants of the Arduino language.

Wrapping within those directives is the class prototype which is a declaration of the class and all methods' (functions) within the class with the method's name and type signature, but without the function body. Methods and properties (variables) can be classified as public which provide a set of APIs for accessing the class methods, or private for those can only be access within the class.

In addition to all the methods, there is a class construct, the class construct will be the first code to run when a class instance is instantiated. The .h header file use C++ language notation, so the construct has the same name as the class, but no return type.

All the methods within our Arduino sketch are public accessible except the _write() which only called within the class. Most of the constants and variable(s) need not be accessed outside of the library.

LCD5110.h

#ifndef LCD5110_h
#define LCD5110_h

#include "Arduino.h"
#include "SPI.h"

#define ON HIGH
#define OFF LOW
#define CMD LOW
#define DATA HIGH
#define LCD_WIDTH 84
#define LCD_HEIGHT 48

class LCD5110
{
  public:
    LCD5110();
    void begin(const uint8_t dc, const uint8_t led);
    void clear(void);
    void cursor(uint8_t row, uint8_t col);
    void backlight(const uint8_t state);
    void inverse(const uint8_t inv);
    void printStr(const char *str);
    void printImage(const char *image);
  private:
    void _write(const uint8_t mode, char data);
    boolean _inverse;
    uint8_t _DC;
    uint8_t _BACKLIGHT;
};

#endif

Step 2.b - Arduino library source file (.cpp)

The Arduino library source file starts with one basic preprocessor directive #include "LCD5110.h".

On the Arduino sketch that we previous developed, we hardcoded every pin assignment with #define preprocessor macro, this works fine for me because I'm the only user for the sketch. However when we develop a library, we are targeting for users other than myself, and the library will likely to be used on different Arduino boards. Each Arduino boards has its own pin definition in a file called pins_arduino.h in its Arduino Core implementation, this will make sure that when you use constant MOSI, it will match to the correct pin of the Arduino board you are using. But there are two pins that in our sketch that would likely need to be user-defined, that is the DC (Data/Command) pin and the BACKLIGHT pin for the LCD5110 module, almost any digital I/O pin on the Arduino can be used for such purpose, so we will need to let user to define those pins prior using the library.

There are two ways to allow user to define the pins, one is through the class construct, where user can pass in the pins as arguments when create an instance of our LCD5110 class, and this is quite common way in C++ for creating an instance and initialise it at the same time, but it is not a good idea for Arduino Library class.

The Arduino Style Guide for Writing Libraries recommends to use begin() method to initialise a library instance, the reason being that if you create a class instance at global level before the setup(), the class construct will run before Arduino initialisation if you understand how Arduino main() is setup. What this means is that whatever you try to setup may not existed or will be overrides by Arduino init() function (which is run before setup()). This is why you often see in Arduino that create an instance is separate from initialisation of the instance, and the initialisation is often done with the begin() method of the instance.

We will move all the codes that previously in our Arduino sketch setup() functions into the newly created begin() method and pass in the pin assignments for DC and BACKLIGHT as function arguments.

#define DC 8
#define BACKLIGHT 7

// create an instance of LCD5110 class
LCD5110 lcd;

void setup() {
  lcd.begin(DC, BACKLIGHT);  // setup the instance
}

void loop() {
  // use the instance by calling various methods
}

The FONT_TABLE was previous declared as a global-accessible variable, we will move it into where it is used within the printStr() method and prefix it with static modifier so its memory space will only get allocated once throughout multiple call of the method.

The rest of the code in our sketch does not need further modification except that all methods need to be prefixed with class name for name spacing purpose based on C++ standard.

LCD5110.cpp

#include "LCD5110.h"

LCD5110::LCD5110() 
{
}

void LCD5110::begin(const uint8_t dc, const uint8_t led)
{
  _DC = dc;
  _BACKLIGHT = led;

  pinMode(_DC, OUTPUT);
  pinMode(_BACKLIGHT, OUTPUT);
  pinMode(SS, OUTPUT);
  digitalWrite(SS, HIGH);

  SPI.begin();    //set SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high.

  _write(CMD, 0x21);  // Set Extended Command set
  _write(CMD, 0xb2);  // Set Vlcd to 6v (LCD Contrast)
  _write(CMD, 0x13);  // Set voltage bias system 1:48 (Viewing Angle)
  _write(CMD, 0x20);  // Set Normal Command set
  clear(); // Clear all display memory and set cursor to 1,1
  _write(CMD, 0x09);  // Set all pixels ON
  _write(CMD, 0x0c);  // Set display mode to Normal
}

void LCD5110::clear(void) {
  cursor(1, 1);
  for (int pixel=(LCD_WIDTH * LCD_HEIGHT / 8); pixel > 0; pixel--) {
    _write(DATA, 0x00);
  }
}

void LCD5110::cursor(uint8_t row, uint8_t col) {
  if ( (row < 1 | row > LCD_HEIGHT / 8) | (col < 1 | col > LCD_WIDTH / 6)) {
    return;
  }
  _write(CMD, 0x40 | ( row - 1) );
  _write(CMD, 0x80 | ( col - 1) * 6 );
}

void LCD5110::_write(const uint8_t mode, char data) {
  SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0));
  digitalWrite(SCE, LOW);
  digitalWrite(DC, mode);  //HIGH = Data mode, LOW = Command mode
  if (mode == HIGH & _inverse == ON) {
      data = ~ data;
  }
  SPI.transfer(data);
  digitalWrite(SCE, HIGH);
  SPI.endTransaction();
}

void LCD5110::backlight(const uint8_t state) {
  digitalWrite(BACKLIGHT, state);
}

void LCD5110::inverse(const uint8_t inv) {
  _inverse = inv;
}

void LCD5110::printImage(const char *image) {
  cursor(1,1);
  for (int i = 0; i < (LCD_WIDTH * LCD_HEIGHT / 8); i++) {
    _write(DATA, image[i]);
  }
}

void LCD5110::printStr(const char *str) {
  static const unsigned char FONT_TABLE [][5] = {
    { 0x00, 0x00, 0x00, 0x00, 0x00 }, // 0x20, space
    { 0x00, 0x00, 0x5f, 0x00, 0x00 }, // 0x21, !
    { 0x00, 0x07, 0x00, 0x07, 0x00 }, // 0x22, "
    { 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // 0x23, #
    { 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // 0x24, $
    { 0x23, 0x12, 0x08, 0x64, 0x62 }, // 0x25, %
    { 0x36, 0x49, 0x55, 0x22, 0x50 }, // 0x26, &
    { 0x00, 0x05, 0x03, 0x00, 0x00 }, // 0x27, '
    { 0x00, 0x1c, 0x22, 0x41, 0x00 }, // 0x28, (
    { 0x00, 0x41, 0x22, 0x1c, 0x00 }, // 0x29, )
    { 0x14, 0x08, 0x3E, 0x08, 0x14 }, // 0x2a, *
    { 0x08, 0x08, 0x3E, 0x08, 0x08 }, // 0x2b, +
    { 0x00, 0x50, 0x30, 0x00, 0x00 }, // 0x2c, ,
    { 0x08, 0x08, 0x08, 0x08, 0x08 }, // 0x2d, -
    { 0x00, 0x60, 0x60, 0x00, 0x00 }, // 0x2e, .
    { 0x20, 0x10, 0x08, 0x04, 0x02 }, // 0x2f, /
    { 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0x30, 0
    { 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 0x31, 1
    { 0x42, 0x61, 0x51, 0x49, 0x46 }, // 0x32, 2
    { 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 0x33, 3
    { 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 0x34, 4
    { 0x27, 0x45, 0x45, 0x45, 0x39 }, // 0x35, 5
    { 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 0x36, 6
    { 0x01, 0x71, 0x09, 0x05, 0x03 }, // 0x37, 7
    { 0x36, 0x49, 0x49, 0x49, 0x36 }, // 0x38, 8
    { 0x06, 0x49, 0x49, 0x29, 0x1E }, // 0x39, 9
    { 0x00, 0x36, 0x36, 0x00, 0x00 }, // 0x3a, :
    { 0x00, 0x56, 0x36, 0x00, 0x00 }, // 0x3b, ;
    { 0x08, 0x14, 0x22, 0x41, 0x00 }, // 0x3c, <
    { 0x14, 0x14, 0x14, 0x14, 0x14 }, // 0x3d, =
    { 0x00, 0x41, 0x22, 0x14, 0x08 }, // 0x3e, >
    { 0x02, 0x01, 0x51, 0x09, 0x06 }, // 0x3f, ?
    { 0x32, 0x49, 0x59, 0x51, 0x3E }, // 0x40, @
    { 0x7E, 0x11, 0x11, 0x11, 0x7E }, // 0x41, A
    { 0x7F, 0x49, 0x49, 0x49, 0x36 }, // 0x42, B
    { 0x3E, 0x41, 0x41, 0x41, 0x22 }, // 0x43, C
    { 0x7F, 0x41, 0x41, 0x22, 0x1C }, // 0x44, D
    { 0x7F, 0x49, 0x49, 0x49, 0x41 }, // 0x45, E
    { 0x7F, 0x09, 0x09, 0x09, 0x01 }, // 0x46, F
    { 0x3E, 0x41, 0x49, 0x49, 0x7A }, // 0x47, G
    { 0x7F, 0x08, 0x08, 0x08, 0x7F }, // 0x48, H
    { 0x00, 0x41, 0x7F, 0x41, 0x00 }, // 0x49, I
    { 0x20, 0x40, 0x41, 0x3F, 0x01 }, // 0x4a, J
    { 0x7F, 0x08, 0x14, 0x22, 0x41 }, // 0x4b, K
    { 0x7F, 0x40, 0x40, 0x40, 0x40 }, // 0x4c, L
    { 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // 0x4d, M
    { 0x7F, 0x04, 0x08, 0x10, 0x7F }, // 0x4e, N
    { 0x3E, 0x41, 0x41, 0x41, 0x3E }, // 0x4f, O
    { 0x7F, 0x09, 0x09, 0x09, 0x06 }, // 0x50, P
    { 0x3E, 0x41, 0x51, 0x21, 0x5E }, // 0x51, Q
    { 0x7F, 0x09, 0x19, 0x29, 0x46 }, // 0x52, R
    { 0x46, 0x49, 0x49, 0x49, 0x31 }, // 0x53, S
    { 0x01, 0x01, 0x7F, 0x01, 0x01 }, // 0x54, T
    { 0x3F, 0x40, 0x40, 0x40, 0x3F }, // 0x55, U
    { 0x1F, 0x20, 0x40, 0x20, 0x1F }, // 0x56, V
    { 0x3F, 0x40, 0x38, 0x40, 0x3F }, // 0x57, W
    { 0x63, 0x14, 0x08, 0x14, 0x63 }, // 0x58, X
    { 0x07, 0x08, 0x70, 0x08, 0x07 }, // 0x59, Y
    { 0x61, 0x51, 0x49, 0x45, 0x43 }, // 0x5a, Z
    { 0x00, 0x7F, 0x41, 0x41, 0x00 }, // 0x5b, [
    { 0x55, 0x2A, 0x55, 0x2A, 0x55 }, // 0x5c, back slash
    { 0x00, 0x41, 0x41, 0x7F, 0x00 }, // 0x5d, ]
    { 0x04, 0x02, 0x01, 0x02, 0x04 }, // 0x5e, ^
    { 0x40, 0x40, 0x40, 0x40, 0x40 }, // 0x5f, _
    { 0x00, 0x01, 0x02, 0x04, 0x00 }, // 0x60, `
    { 0x20, 0x54, 0x54, 0x54, 0x78 }, // 0x61, a
    { 0x7F, 0x48, 0x44, 0x44, 0x38 }, // 0x62, b
    { 0x38, 0x44, 0x44, 0x44, 0x20 }, // 0x63, c
    { 0x38, 0x44, 0x44, 0x48, 0x7F }, // 0x64, d
    { 0x38, 0x54, 0x54, 0x54, 0x18 }, // 0x65, e
    { 0x08, 0x7E, 0x09, 0x01, 0x02 }, // 0x66, f
    { 0x0C, 0x52, 0x52, 0x52, 0x3E }, // 0x67, g
    { 0x7F, 0x08, 0x04, 0x04, 0x78 }, // 0x68, h
    { 0x00, 0x44, 0x7D, 0x40, 0x00 }, // 0x69, i
    { 0x20, 0x40, 0x44, 0x3D, 0x00 }, // 0x6a, j
    { 0x7F, 0x10, 0x28, 0x44, 0x00 }, // 0x6b, k
    { 0x00, 0x41, 0x7F, 0x40, 0x00 }, // 0x6c, l
    { 0x7C, 0x04, 0x18, 0x04, 0x78 }, // 0x6d, m
    { 0x7C, 0x08, 0x04, 0x04, 0x78 }, // 0x6e, n
    { 0x38, 0x44, 0x44, 0x44, 0x38 }, // 0x6f, o
    { 0x7C, 0x14, 0x14, 0x14, 0x08 }, // 0x70, p
    { 0x08, 0x14, 0x14, 0x18, 0x7C }, // 0x71, q
    { 0x7C, 0x08, 0x04, 0x04, 0x08 }, // 0x72, r
    { 0x48, 0x54, 0x54, 0x54, 0x20 }, // 0x73, s
    { 0x04, 0x3F, 0x44, 0x40, 0x20 }, // 0x74, t
    { 0x3C, 0x40, 0x40, 0x20, 0x7C }, // 0x75, u
    { 0x1C, 0x20, 0x40, 0x20, 0x1C }, // 0x76, v
    { 0x3C, 0x40, 0x30, 0x40, 0x3C }, // 0x77, w
    { 0x44, 0x28, 0x10, 0x28, 0x44 }, // 0x78, x
    { 0x0C, 0x50, 0x50, 0x50, 0x3C }, // 0x79, y
    { 0x44, 0x64, 0x54, 0x4C, 0x44 }, // 0x7a, z
    { 0x00, 0x08, 0x36, 0x41, 0x00 }, // 0x7b, {
    { 0x00, 0x00, 0x7f, 0x00, 0x00 }, // 0x7c, |
    { 0x00, 0x41, 0x36, 0x08, 0x00 }, // 0x7d, }
    { 0x10, 0x08, 0x08, 0x10, 0x08 }, // 0x7e, ~
    { 0x78, 0x46, 0x41, 0x46, 0x78 }  // 0x7f, DEL
  };
  int p = 0;
  while (str[p]!='\0') {
    if ( (str[p] >= 0x20) & (str[p] <= 0x7f) ) {
      for (int i = 0; i < 5; i++) {
        _write(DATA, FONT_TABLE[str[p] - 32][i]);
      }
      _write(DATA, 0x00);
    }
    p++;
  }
}

We are now have a fully functional Arduino library. The file directories should looks like this:

Libraries
|-- LCD5110
    |-- examples
        |-- LCD5110_demo
            |-- LCD5110_demo.pde
    |-- src
        |-- LCD5110.cpp
        |-- LCD5110.h

Launch Arduino IDE and open a new sketch, navigate to Sketch --> Include Library, we should see our library LCD5110 under Contributed Libraries.

Selecting our newly created library from the Sketch --> Include Library --> LCD5110 would result in adding a line #include <LCD5110.h> into our sketch. This is how we can use our library.

Click on the Verify button (the first button with "tick" sign on the left top of the Arduino IDE) to compile the code, there should be no error message generated from the verify process.

Step 3 - Create example on using your Arduino library

Step 3 is to create an example sketch to show how to use the Arduino library.

Arduino library recommends to create an Examples directory within the Arduino library directory, and for each example sketch requires its own directory too. In our case, I would name the example sketch as LCD5110_demo.pde (please noted that it is not .ino file extension as normal sketch) and put it under LCD5110/Examples/LCD5110_demo/ directory.

The example is not really your API documentation, but it provide a quick reference on how to utilise the library. The example program starts with preprocessor to include the library, follow by creating an instance of the library class. There is nothing for the setup() function when using our library because it all set at the creation of class instance. The loop() function of our example demo program is very much copy from the loop() of our original Arduino sketch.

LCD5110_demo.pde

#include <LCD5110.h>

/* See README on https://github.com/e-tinkers/LCD-5110-Arduino-library for API documentation*/

#define DC 8           // pin use for LCD DC (Data/Command mode) control
#define BACKLIGHT 7    // pin use for control backlight

// Use https://www.e-tinkers.com/nokia5110-lcd-image-creator/ to create your own logo
const char eTinkersLogo[504] = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xf0, 0xf0, 0xf8, 0xfc, 0xfc, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0xfc, 0xfc, 0xf8, 0xf0, 0xf0, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xf8, 0x10, 0x10, 0xa0, 0x20, 0x40, 0x40, 0x80, 0xfc, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x82, 0x82, 0x44, 0x44, 0x28, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3f, 0x10, 0x11, 0x09, 0x09, 0x04, 0x04, 0x02, 0x7e, 0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0xf0, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0x78, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf0,
  0x00, 0x01, 0x07, 0x0f, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x0f, 0x0f, 0x07, 0x01, 0x00
};

LCD5110 lcd;

void setup() {
  lcd.begin((DC, BACKLIGHT);
}

void loop() {
  lcd.cursor(2, 2);   // Set cursor to row 2, column 2
  lcd.printStr("Hello World!!");
  lcd.cursor(4, 2);
  lcd.printStr("e-tinkers.com");
  lcd.inverse(ON);
  lcd.cursor(6,1);
  lcd.printStr("** Nov 2017 **");
  lcd.inverse(OFF);
  delay(5000);

  lcd.backlight(ON);
  lcd.printImage(eTinkersLogo);
  delay(5000);
  lcd.backlight(OFF);
  lcd.clear();
}

Re-launch your Arduino IDE, now you can access the example from "File/Examples/..." drop down menu.

Step 4 - Create Library meta data files

This step is not essential for the use of the library and therefore optional but we suggest to have it as a good software library/package management best practices.

Arduino published its Library Specification since IDE v1.5. The most significant addition to the documentation is the ability to add information about a library itself through a properties file called library.properties. This file allows the Library Manager to search and install a library and its dependencies in an easy and automated way. It must be located in the root of the library folder.

The valid property/value pairs can be used for the file are described in the documentation so I won't repeat here. But pay attention to a few things:
- For name property, be consistent to your library name, the name can be more descriptive and longer than your library name, but be consistent to your .cpp naming. The reason being that when user search an library to install from Library Manager, the name property will be used for the search.
- If your library only works for a particular architectures, be sure that you add the architectures and specify the architecture(s) (e.g. avr (for Arduino), stm32, esp32, etc.), by default if you don't specify the architeture that your library is compatible with, Arduino Library Manager is going to assume that your library works for all architectures (i.e. architectures=*).
- There are minimum set of properties are mandatory, however it does not mentioned what are the properties are mandatory, but if you forgot to have paragraph properties, you will get somewhat cryptic error message "Missing 'paragraph' from library" on Arduino IDE.

Our LCD5110 library will have a library.properties file like this:

name=LCD5110/PCD8544 Library
version=2.0.0
author=Henry Cheung
maintainer=e-tinkers.com
sentence=A simple and easy-to-use library for LCD5110 (a.k.a. Nokia5110, PCD8544) display module.
paragraph=Unlike other libraries that try to be swiss-knift for all LCD modules, this library targeting LCD5110 with compact code and reliable SPI interface.
category=LCD, display
url=https://github.com/e-tinkers/LCD-5110-Arduino-library
architectures=avr

As PlatformIO IDE is gaining attraction in the past a couple of years, many Arduino libraries published in github are now ensure that their libraries meet the PlatformIO Library Manager requirements in addition in meeting Arduino Library Specification.

PlatformIO uses an library.json file for library meta data. The requirement is described in Create Library section of PlatformIO wiki. The naming convention used by library.json is unfortunately not compatible with library.properties, but can be easily correlated and please try to be consistent on wording with your library.properties file.

{
  "name": "Simple LCD5110/PCD8544 Library",
  "keywords": "LCD, display",
  "description": "A simple and easy-to-use library for LCD5110 (a.k.a. Nokia5110, PCD8544) display module",
  "repository": {
    "type": "git",
    "url": "https://github.com/e-tinkers/LCD-5110-Arduino-library"
  },
  "version": "2.0.0",
  "authors": {
    "name": "Henry Cheung",
    "url": "https://www.e-tinkers.com"
  },
  "frameworks": "arduino",
  "platforms": ["atmelavr"]
}

Step 5 - Create Keywords.txt (optional)

Step 5 is optional, it provides Arduino IDE syntax highlighting for the newly created Arduino library using Keywords.txt file.

If you try to write a sketch using our library's class and methods, none of our library's class and methods are highlighted in color as other Arduino functions and other libraries shown, but we can gives it a little help by creating a list of keywords.

Each line has the name of the keyword, followed by a tab (not spaces), followed by the kind of keyword. Class and datatypes (if any) should be classify as "KEYWORD1", and all methods and functions should be categories as "KEYWORD2", Constants and variables should be categories as "LITERAL1". Store the Keywords.txt in the Library/LCD5110/ directory. Relaunch Arduino IDE to see the effect.

# Syntax Highlighting For LCD5110 library

# Class and Datatypes (KEYWORD1)
LCD5110		KEYWORD1

# Methods and Functions (KEYWORD2)
begin	KEYWORD2
clear	KEYWORD2
cursor	KEYWORD2
write	KEYWORD2
backlight	KEYWORD2
inverse	KEYWORD2
printStr	KEYWORD2
printImage	KEYWORD2

# Constants (LITERAL1)

Step 6 - Create an README.md

The final step is optional but again a good software development practice. Arduino IDE historically emphasises on including examples in the library publishing and distribution, but lack of emphasising on documentation creation. As the result, many of the Arduino Libraries out there lack of any documentation on how to use the library other than example codes.

We highly suggest to create README.md and uses it to provide API documentation for your library. README.md is a markdown document that often is the first file a user read if they find your library on github, or when you using PlatformIO IDE Library Manager to search for an library, the README.md information will be used to show on the search result. If one README.md is insufficient for documentation, you could create an doc sub-directory within the library directory to provide additional documentation.

We have successfully created an Arduino library.

The purpose of the library shown in this article is to teach on how to create an Arduino Library, the code is however not optimised with best performance and memory usage considerations. For learning purpose, you can download the code here and add it to your Arduino Library directory. However, If you intend to use the code in your program, it is better to download the LCD-5110-library from my github as it has further enhancement that is not covered in this article.

One comment by reader

Comments are closed.