How to create a library

I have been downloading and installing libraries , my question is how does one goes about creating a library.
Let's say a library that blinks an LED , you just need to put the time delay.

Also , when I open libraries I see some files with c programming syntaxes.
Pls can someone demystify libraries in Arduino for me

Your post was MOVED to its current location as it is more suitable

A delay would be about the last thing you would want to put into a library

The difference between C and C++ can be subtle

Can you explain what libraries are and how to go about creating one.
Also what skill /knowledge should I have to create a library

A library is simply a means of implementing a common functionality (like a driver for a sensor, or a set of mathematical or logical functions) in a separate set of source files, such that they can be simply integrated into new projects.

They can be implemented in many different ways, as classes or a simple collection of functions.

Pls what are classes
Functions are callable group of code but classes, I don't know

Classes are collections of code and data.
Understanding then is fundamental to C++.

So I do need to take tutorials on classes in c++

How about writing a library that allows you blink an LED ,
When you install the library in the Arduino sketch ,you just need to put the required delay to make the led blink

Yes, I think that would be a good place to start.

I found this tutorial to be a useful introduction to writing Arduino libraries:

The libraries can be loaded into a sketch where they can be viewed, where you can see how others created such and such library.

When you are first starting out developing a library you can start with a sketch. Since you want your library to blink an LED, let's start with the BlinkWithoutDelay example. You can find the full source under File->Examples->02.Digital->BlinkWithoutDelay so I am removing most of the comments for compactness.

const int ledPin =  LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000; 

void setup() {
  pinMode(ledPin, OUTPUT);
}
void loop()
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

The next step is to move the guts of the library behavior into a function. This sketch does exactly the same thing as before.

const int ledPin =  LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;

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

void blink(unsigned long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

void loop()
{
  blink(1000);
}

Now comes the 'library' split. You start but creating a new tab. At the right end of the bar just above the sketch is a little triangle in a square. Click on that and select "New Tab". name the new tab "BlinkLib.cpp". Now cut the 'blink' function out of the main tab and paste it into the new tab.

If you try to Verify your sketch now you will find some errors. The first is:

BlinkLib.cpp:3:33: error: 'millis' was not declared in this scope
   unsigned long currentMillis = millis();
                                 ^~~~~~

This is because, unlike a '.ino' file '.cpp' files don't get
#include <Arduino.h>
automatically inserted at compile time. Add that line to the top of BlinkLib.cpp and do another Verify. The next error is:

BlinkLib.cpp:6:23: error: 'previousMillis' was not declared in this scope
   if (currentMillis - previousMillis >= interval)
                       ^~~~~~~~~~~~~~

This is because that declaration didn't get moved to the library. In this case, only the library needs it so move that declaration to the top of BlinkLib.cpp. While you're at it, move 'ledState' and 'interval', too. Then try another Verify.

Now you get the error:

sketch_jan05a:13:3: error: 'blink' was not declared in this scope
   blink(1000);
   ^~~~~

That's because the main sketch no longer contains a declaration for the blink() function. This is a good time to create another tab that shares the library declarations with the sketch that uses the library. Create a new tab named BlinkLib.h (h for 'header').
In that new tab, put the line:
void blink(unsigned long);
Then in the main sketch, add the line:
#include "BlinkLib.h"
This will allow the main sketch to see a declaration of "blink". The compiler will know what to do.

That leaves you with the next error:

BlinkLib.cpp:21:18: error: 'ledPin' was not declared in this scope
     digitalWrite(ledPin, ledState);
                  ^~~~~~

We used 'ledPin' in setup() so we left the declaration there, but now BlinkLib.cpp needs the declaration. We need a way for setup() to set the pin mode so we create another function in BlinkLib.cpp:

void setPin(int pin)
{
  ledPin = pin;
  pinMode(ledPin, OUTPUT);
}

Put the declaration into BlinkLib.h:
void setPin(int);
Move the declaration of 'ledPin' into BlinkLib.cpp. Now you can have setup() call setPin().

Alltogether you have:
Main Sketch:

#include "BlinkLib.h"

void setup()
{
  setPin(LED_BUILTIN);
}

void loop()
{
  blink(1000);
}

Library header:

void blink(unsigned long);
void setPin(int);

Library source:

#include <Arduino.h>
#include "BlinkLib.h"

int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 1000;
int ledPin =  LED_BUILTIN;

void setPin(int pin)
{
  ledPin = pin;
  pinMode(ledPin, OUTPUT);
}

void blink(unsigned long)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}
3 Likes

Now that you have BlinkLib.h and BlinkLib.cpp you can move them into libraries/BlinkLib and your sketch will still compile. You can then move your sketch to libraries/BlinkLib/examples/test/test.ino and it will show up in the IDE under: File->Examples->BlinkLib->test

One problem with the library is that you can only blink one LED because the library only has one set of global variables. To fix that, we 'encapsulate' the functionality of the library into an 'object'. You can then make multiple 'instances' of the object, each with its own variables.

Let's create an object class named 'Blinker'. This goes into BlinkLib.h in place of the shared function prototypes. Note that now BlinkLib.h needs some definitions from Arduino.h ('byte', 'LOW', 'pinMode'...) so we include Arduino.h at the top.

#include <Arduino.h>

class Blinker
{
  public:
    Blinker(const byte pin) : pin(pin) 
    {
      ledState = LOW; 
      previousMillis = 0;
    }

    void begin()
    {
      pinMode(pin, OUTPUT);
      previousMillis = millis();
    }

    void blink(const unsigned long interval);

  private:
    const byte pin;
    int ledState;
    unsigned long previousMillis;
};

The "setPin()" functionality can be done in the 'constructor' so we specify the pin when we create each instance. For a global instance (defined outside a function) it is not safe to call Arduino functions in the constructor so we add a "begin()" method to be called to do any Arduino calls needed for setup.

The BlinkLib.cpp file now contains definitions for whatever instance functions are not defined in the class declaration.

#include "BlinkLib.h"

void Blinker::blink(const unsigned long interval)
{
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(pin, ledState);
  }
}

The test sketch doesn't have to change much. It sets the pin in the declaration of the instance, calls .begin() for each instance in setup(), and calls the .blink() method repeatedly in loop(), just like the old blink() function but there can be more of them.

#include "BlinkLib.h"

Blinker blinker(LED_BUILTIN);  // Blink the built-in LED
Blinker blinker2(9);  // Blink the LED on Pin 9

void setup()
{
  blinker.begin();
  blinker2.begin();
}

void loop()
{
  blinker.blink(1000);
  blinker2.blink(750);
}

Let us write non-object codes first and then object-based codes to turn-ON Air Pump-1 of Fig-1 when the TS-1’s temperature goes above 30.50 degC. The Air Pump-1 will turn-OFF when temperature goes below 30.50 degC. TS-1 (Temperature Sensor-1) is embedded within the motor winding of Air Pump-1 to monitor over-heating. In the course of performing this task, we will come across the techniques of creating Library.


Figure-1:

1(a) Let us create a "non-object version" sketch to operate one-pump/on-sensor setup as per diagram of Fig-1. (Note that I have avoided saying "non-class version" as class is a keyword which we use to create user-defined data type" from which we create objects on which we apply methods to do some works like pump-on/pump-off.)

(b) Upload the following non-object based sketch (LD1 is used to simulate relay-K1) into UNO.

#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

void setup()
{
  Serial.begin(9600);
  sensors.begin();
}
void loop()
{ 
  sensors.requestTemperatures(); // Send the command to get temperatures
  float tempC = sensors.getTempCByIndex(0);
  Serial.print("Temperature for the device 1 (index 0) is: ");
  Serial.println(tempC, 2);
  if(tempC >= 30.50)  //test temperature
  {
    digitalWrite(2, HIGH);  //Air Pump-1/LD1 is ON
  }
  else
  {
    digitalWrite(2, LOW);  //Air Pump-1/LD1 is OFF
  }
  delay(1000);
}

(c) Rub the temp sensor by fingers and check that LD1 is ON when temperature goes belwo 30.50 degC (test temperature).

(2) Convert the sketch of Step-(1)(b) using objects.
(a) Create a "user-defined data type (UDT)" named AirPump using class keyword. The "user-defined data type" or simply "data type" refers to a conglomerate structure that contains variables (data) and functions/methods to process those data.)

class AirPump
{
   //------------------------
};

We use “class keyword” to declare a User Defined Data Type (UDT) named AirPump.

(b) From the data type named AirPump, let us create an object named pump1. The “pump1" refers to Air Pump-1 (Fig-1) which turns ON and turns OFF based on temperature supplied by TS-1 of Fig-1.

AirPump  pump1; 

(c) Let us name a method as pumpON() that we wish to apply on the object named pump1 to turn on Air Pump-1 of Fig-1 when temperature (supplied by TS-1) goes below 24.50 degC (test purpose).

(d) Lat us name another method as pumpOFF() that we wish to apply on the object named pump1 to turn off Air Pump-1 of Fig-1 when temperature (supplied by TS-1) goes above 24.50 degC (test value).

(e) We may now populate the the empty structure of Step-4 as follows:

class AirPump    //class is a keyword; we user it to get UDT (User Defined Data Type) = AirPump
{
     private:
     int   relay1Pin;                       //this is DPin-2 of Fig-1
     
     public:
     AirPump(int DPin):relay1Pin(DPin){}   //constructor that always comes with data type
     void   varSetup();                                        //function to intialize variables
     void   pumpON();                                 //function/method to turn on Air Pump-1
     void   pumpOFF();                               //function/methd to turn off Air Pump-1 
};

The “class" keyword helps to declare data structure with private, public, and protected keywords in which only the functions under “public" keyword can access the variables under “private" keyword.

(f) Let us define the functions of the structure of Step-(2)(e).
(i) varSetup()

void AirPump::varSetup()  //:: = Scope Operator
{
     pinMode(relay1Pin, OUTPUT);       //direction of DPin-2 is output 
}

(ii) pumpON()

void AirPump::pumpON()
{
      digitalWrite(relay1Pin, HIGH);     //Air Pump-1/LD1 is ON
}

(iii) pumpOFF()

void AirPump::pumpOFF()
{
      digitalWrite(relay1Pin, LOW);       //Air Pump-1 id OFF
}

(3) Create a Header File using Notepad application and save as AirPump.h. This header file will contain the following declarations/structure.

class AirPump	//class = keyword ; AirPump = User-defined data type (UDT) or className or classTag
{
     private:
     int   relay1Pin;                       //this is DPin-2 of Fig-1
     
     public:
     AirPump(int DPin):relay1Pin(DPin){}   //constructor that always comes with data type
     void   varSetup();                                        //function to intialize variables
     void   pumpON();                                 //function/method to turn on Air Pump-1
     void   pumpOFF();                               //function/methd to turn off Air Pump-1 
};

(4) Create a C++ File using Notepad application and save as AirPump.cpp. This file will contain the following lines.

#include<AirPump.h>
#include<Arduino.h>

void AirPump::varSetup()
{
  pinMode(relay1Pin, OUTPUT);      //direction set
}

void AirPump::pumpON()
{
  digitalWrite(relay1Pin, HIGH);
}
void AirPump::pumpOFF()
{
  digitalWrite(relay1Pin, LOW);
}

(5) Make a ZIF File for AirPump.h and AirPump.cpp. It will be saved as AirPump.zip.

(6) Open Arduino IDE and create the following sketch/source code and save as AirPump. This is the final sketch. Include the AirPump.zip file in the IDE and sketch.

#include <AirPump.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);//-----------------
DallasTemperature sensors(&oneWire);

AirPump pump1(2);       
//we create object named pump1 from AirPump constructor DPin-2 = relay1Pin;

void setup()
{
  Serial.begin(9600);
  pump1.varSetup();  //pin direction set for relay1Pin object pump1(2) is called upon with DPin-2
  //-------------
  sensors.begin();        //DS18B20 sensor is enabled
}

void loop()
{
  sensors.requestTemperatures(); // Send the command to get temperatures
  float tempC = sensors.getTempCByIndex(0);
  Serial.println(tempC, 2);
  if (tempC >= 30.50)
  {
    pump1.pumpON();		//Air Pump-1 is ON
  }
  else
  {
    pump1.pumpOFF();      //Air Pump-1 is OFF
  }
  delay(1000);			//test interval
}

(7) Upload the sketch of Step-6 into UNO.

(8) Check that the Serial Monitor shows room temperature.

(9) Rub temperature sensor by fingers and check that LD1 becomes ON/OFF accordingly.