One of Arduino’s greatest strengths is its massive ecosystem of reusable libraries. Understanding Arduino library creation — from installing existing ones to writing and publishing your own — dramatically accelerates every project you build. Whether you want to package your sensor driver for personal reuse across projects, share it with the community on GitHub, or even submit it to the Arduino Library Manager, this guide walks you through the complete process.
Table of Contents
- Installing Arduino Libraries
- Understanding Library File Structure
- Writing Your First Arduino Library
- Designing the Header File
- Writing the Implementation File
- Adding Examples to Your Library
- Publishing to GitHub and Arduino Library Manager
- Frequently Asked Questions
Installing Arduino Libraries
Before writing your own, you need to know how to install existing libraries — which you’ll rely on constantly for sensors, displays, and communication protocols.
Method 1: Arduino Library Manager (Recommended)
- Open Arduino IDE
- Go to Tools → Manage Libraries (or Sketch → Include Library → Manage Libraries)
- Search for the library name (e.g., “DHT sensor library”)
- Click Install
- If the library has dependencies, a popup will ask to install them too — always click “Install all”
Method 2: Install from ZIP File
When a library isn’t in the Library Manager (e.g., a custom fork from GitHub):
- Download the ZIP from GitHub (Code → Download ZIP)
- In Arduino IDE: Sketch → Include Library → Add .ZIP Library
- Select the downloaded ZIP file
- The library is immediately available under Sketch → Include Library
Method 3: Manual Installation
Unzip the library folder into your Arduino libraries directory:
- Windows:
C:UsersYourNameDocumentsArduinolibraries - macOS:
~/Documents/Arduino/libraries/ - Linux:
~/Arduino/libraries/
Restart the Arduino IDE after manual installation.
Finding Your Libraries Folder
In Arduino IDE: File → Preferences — the “Sketchbook location” path shows where your libraries folder lives.
Understanding Library File Structure
A proper Arduino library follows a standardised directory layout that the Arduino IDE expects:
MyLibrary/
├── src/
│ ├── MyLibrary.h (header file — class declaration)
│ └── MyLibrary.cpp (implementation file — method bodies)
├── examples/
│ └── BasicUsage/
│ └── BasicUsage.ino (example sketch)
├── library.properties (library metadata)
├── keywords.txt (syntax highlighting definitions)
└── README.md (documentation)
The src/ folder is required for modern libraries (library format version 2.1+). Older flat-layout libraries (header and cpp directly in the root) still work but are considered legacy.
The library.properties File
This metadata file is required for Library Manager submission and good practice in any shared library:
name=MyLibrary
version=1.0.0
author=Your Name <[email protected]>
maintainer=Your Name <[email protected]>
sentence=A one-line description of what this library does.
paragraph=Longer description providing more details about the library features.
category=Sensors
url=https://github.com/yourusername/MyLibrary
architectures=avr,esp32,samd
includes=MyLibrary.h
depends=
Category options for Arduino Library Manager: Display, Communication, Signal Input/Output, Sensors, Device Control, Timing, Data Processing, Data Storage, Other.
Writing Your First Arduino Library
We’ll build a complete library for the DHT11/DHT22 temperature sensor family as a realistic example. Even though excellent DHT libraries already exist, reimplementing one is the perfect teaching project because the sensor protocol is well-documented.
What our library will do:
- Read temperature and humidity from DHT11/DHT22 sensors
- Return a clean struct with both values
- Provide error codes for timeout and checksum failures
- Support both 3.3V and 5V operation
Designing the Header File
The header file (.h) is the public API of your library. Think of it as a contract with the user. It should be clean, well-commented, and reveal only what the user needs to know — implementation details stay in the .cpp file.
// src/SimpleDHT.h
#ifndef SIMPLE_DHT_H
#define SIMPLE_DHT_H
#include <Arduino.h>
// Sensor type constants
#define DHT_TYPE_11 11
#define DHT_TYPE_22 22
// Error codes
#define DHT_OK 0
#define DHT_ERR_TIMEOUT 1
#define DHT_ERR_CRC 2
// Reading result struct
struct DHTReading {
float temperature; // Celsius
float humidity; // Relative humidity (%)
uint8_t error; // DHT_OK or error code
};
class SimpleDHT {
public:
// Constructor: pin number and sensor type (DHT_TYPE_11 or DHT_TYPE_22)
SimpleDHT(uint8_t pin, uint8_t type = DHT_TYPE_11);
// Read temperature and humidity
// Returns DHTReading with .error set to DHT_OK on success
DHTReading read();
// Minimum interval between reads (milliseconds)
// DHT11 needs at least 1000ms, DHT22 needs at least 2000ms
uint16_t minInterval() const;
private:
uint8_t _pin;
uint8_t _type;
unsigned long _lastReadTime;
// Internal: read one bit from the sensor
uint8_t _readBit(uint32_t timeout);
// Internal: compute and verify checksum
bool _verifyChecksum(uint8_t data[5]);
};
#endif // SIMPLE_DHT_H
Key Header File Best Practices
- Include guards (
#ifndef SIMPLE_DHT_H) prevent double-inclusion errors. Use the library name in uppercase as the guard macro name. - Always include
<Arduino.h>in your header when you use Arduino types (uint8_t,unsigned long, etc.) - Public vs private: Expose only what users need. Internal implementation methods go in
private: - Document your API with comments above each public method — these become the documentation users read
Writing the Implementation File
// src/SimpleDHT.cpp
#include "SimpleDHT.h"
SimpleDHT::SimpleDHT(uint8_t pin, uint8_t type) {
_pin = pin;
_type = type;
_lastReadTime = 0;
}
uint16_t SimpleDHT::minInterval() const {
return (_type == DHT_TYPE_22) ? 2000 : 1000;
}
DHTReading SimpleDHT::read() {
DHTReading result = {0.0f, 0.0f, DHT_OK};
// Enforce minimum interval between reads
if (millis() - _lastReadTime < minInterval()) {
// Return cached result or wait — for simplicity, just return zeros
return result;
}
_lastReadTime = millis();
// Send start signal
pinMode(_pin, OUTPUT);
digitalWrite(_pin, LOW);
delay((_type == DHT_TYPE_22) ? 1 : 18); // 18ms for DHT11, 1ms for DHT22
digitalWrite(_pin, HIGH);
delayMicroseconds(40);
pinMode(_pin, INPUT_PULLUP);
// Read 40 bits (5 bytes) of data
uint8_t data[5] = {0};
for (int byte = 0; byte = 0; bit--) {
// Wait for bit start (LOW phase)
uint32_t t = micros();
while (digitalRead(_pin) == LOW) {
if (micros() - t > 100) { result.error = DHT_ERR_TIMEOUT; return result; }
}
// Measure HIGH duration: 40us = 1
t = micros();
while (digitalRead(_pin) == HIGH) {
if (micros() - t > 100) { result.error = DHT_ERR_TIMEOUT; return result; }
}
if (micros() - t > 40) data[byte] |= (1 << bit);
}
}
// Verify checksum
if (!_verifyChecksum(data)) {
result.error = DHT_ERR_CRC;
return result;
}
// Parse values
if (_type == DHT_TYPE_11) {
result.humidity = data[0];
result.temperature = data[2];
} else {
result.humidity = ((data[0] << 8) | data[1]) * 0.1f;
result.temperature = (((data[2] & 0x7F) << 8) | data[3]) * 0.1f;
if (data[2] & 0x80) result.temperature = -result.temperature;
}
return result;
}
bool SimpleDHT::_verifyChecksum(uint8_t data[5]) {
return data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF);
}
Adding Examples to Your Library
Examples are critical — they’re the first thing users look at when evaluating a library. Every library should have at least one simple, well-commented example:
// examples/BasicUsage/BasicUsage.ino
/*
SimpleDHT - Basic Usage Example
Reads temperature and humidity from a DHT11 sensor
connected to digital pin 2.
Circuit:
- DHT11 VCC → Arduino 5V
- DHT11 DATA → Arduino pin 2 (with 10kΩ pull-up to 5V)
- DHT11 GND → Arduino GND
Created 2024 by Your Name
Released under MIT License
*/
#include <SimpleDHT.h>
SimpleDHT dht(2, DHT_TYPE_11); // Pin 2, DHT11 type
void setup() {
Serial.begin(115200);
Serial.println("SimpleDHT Basic Usage");
}
void loop() {
DHTReading reading = dht.read();
if (reading.error == DHT_OK) {
Serial.print("Temperature: ");
Serial.print(reading.temperature);
Serial.print(" °C | Humidity: ");
Serial.print(reading.humidity);
Serial.println(" %");
} else if (reading.error == DHT_ERR_TIMEOUT) {
Serial.println("Error: Sensor timeout — check wiring");
} else if (reading.error == DHT_ERR_CRC) {
Serial.println("Error: Checksum failure — interference?");
}
delay(2000); // DHT11 needs at least 1 second between reads
}
keywords.txt for Syntax Highlighting
Add a keywords.txt file to make your library’s keywords highlight in the Arduino IDE:
# Datatypes (KEYWORD1 - orange)
SimpleDHT KEYWORD1
DHTReading KEYWORD1
# Methods (KEYWORD2 - brown)
read KEYWORD2
minInterval KEYWORD2
# Constants (LITERAL1 - blue)
DHT_TYPE_11 LITERAL1
DHT_TYPE_22 LITERAL1
DHT_OK LITERAL1
DHT_ERR_TIMEOUT LITERAL1
DHT_ERR_CRC LITERAL1
Publishing to GitHub and Arduino Library Manager
Step 1: Publish to GitHub
- Create a new GitHub repository named after your library (e.g.,
SimpleDHT) - Push your library folder contents to the repository root
- Create a release: on GitHub, click Releases → Create a new release
- Tag the release with a semantic version (e.g.,
v1.0.0) matching yourlibrary.properties - Publish the release
Step 2: Submit to Arduino Library Manager
- Ensure your
library.propertiesfile is complete and correct - Ensure a tagged GitHub release exists
- Go to the Arduino Library Manager GitHub repository:
https://github.com/arduino/library-registry - Open an issue using the “Add Library” template and provide your GitHub repo URL
- The Arduino team reviews and adds your library (usually within a few days)
- Once approved, anyone searching in the Arduino IDE Library Manager will find your library
Library Versioning Best Practices
- Use semantic versioning: MAJOR.MINOR.PATCH (e.g., 1.2.3)
- MAJOR: breaking API changes
- MINOR: new features, backward compatible
- PATCH: bug fixes only
- Update
library.propertiesversion and create a new GitHub release for each published version
Frequently Asked Questions
Can I use C++ classes and templates in Arduino libraries?
Yes, Arduino libraries are standard C++ and can use classes, templates, namespaces, and most C++11/14/17 features supported by the compiler. However, be mindful of code size — templates can significantly increase compiled sketch size on memory-constrained AVR boards.
How do I handle library dependencies?
List dependencies in the depends field of library.properties (comma-separated). The Library Manager will prompt users to install dependencies automatically. In your header, include the dependent library’s header with #include.
What license should I use for my Arduino library?
MIT and GNU LGPLv2.1 are the most common open-source licenses for Arduino libraries. Arduino’s own libraries use LGPLv2.1. MIT is simpler and more permissive. Include a LICENSE file in your repository root.
How do I support multiple Arduino architectures in one library?
Use the architectures=* wildcard in library.properties if your library is architecture-agnostic. Use preprocessor #ifdef ARDUINO_ARCH_AVR, #ifdef ARDUINO_ARCH_ESP32 etc. to provide architecture-specific implementations within the same source files.
Why won’t my library show up after installation?
The Arduino IDE caches library information. After manual installation, always restart the IDE completely. Also verify the library folder name matches the .h file name — the IDE uses this to resolve #include statements. A folder named my_library must contain my_library.h (or the header must be in src/my_library.h).
Get the hardware for your next Arduino library project — Browse our full range of Arduino boards, sensors and modules at Zbotic. Genuine Arduino boards and quality sensors shipped across India to power your next maker creation.
Add comment