Arduino-/IoT-Extra – Bibliotheken selbst implementieren

In dieser Sonderedition des Blogs geht es um das Thema Arduino-Bibliotheken. Leser lernen dabei, eigene Bibliotheken zu erstellen. Mit etwas Grundwissen zu C++ ist das alles kein Problem.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 9 Min.
Von
  • Dr. Michael Stal
Inhaltsverzeichnis

In dieser Sonderedition des Blogs geht es um das Thema Arduino-Bibliotheken. Leser lernen dabei, eigene Bibliotheken zu erstellen. Mit etwas Grundwissen zu C++ ist das alles kein Problem.

In fast allen Folgen der IoT-/Arduino-Reihe haben wir in den vorgestellten Projekten Bibliotheken verwendet. Bibliotheken für Motoransteuerungen, zur Kommunikation oder zum Zugriff auf Sensorik erlauben Entwicklern, sich auf das Wesentliche zu konzentrieren. Sie verstecken die meisten Details hinter einer generischen API-Fassade.

Über die Bibliotheken selbst beziehungsweise über deren Design haben wir uns allerdings nur wenig Gedanken gemacht. Gelegentlich wäre es sinnvoll, die ein oder andere Bibliothek selbst bereitzustellen. Das ist keine Hexenkunst oder schwarze Magie. Der vorliegende Beitrag soll illustrieren, wie sich eigene Bibliotheken für den Arduino zusammenstellen lassen.

Als Grundvoraussetzung sind zumindest Basiskenntnisse über C++ gefragt, zumal die Bibliotheken auf C und C++ fußen.

Als Beispiel möchte ich einen elektronischen Würfel implementieren, der aus 3 LEDs besteht. Die gewürfelte Zahl aus {1, 2, 3, 4, 5, 6} zeigt der Würfel als dreistellige Binärzahl an, also als Element aus der Menge {001, 010, 011, 100, 101, 110}.

Eine Bibliothek macht nur dann Sinn, wenn sie häufig wiederverwendbare Funktionen offeriert. Da sich ein Würfel als Grundlage für Spiele nutzen lässt, erscheint es die Mühe wert, seine Implementierung als Bibliothek anzubieten.

Der elektronische Würfel mit 3 LEDs

Die Klasse Dice im nachfolgendem Listing repräsentiert den Würfel. Die drei Parameter des Konstruktors entsprechen den digitalen Anschlusspins der LEDs. led4 steht für die 22-Stelle des Binärwertes, led2 für die 21-Stelle und led1 für die 20-Stelle.

Die Klassenvereinbarung in der Header-Datei Dice.h ist gemäß eines C++-Idioms in eine #ifndef-Direktive eingebunden, um mehrfaches Inkludieren dieser Datei zu verhindern.

Die öffentliche Methode roll() repräsentiert das eigentliche Würfeln. Die private Methode controlLED(int led, bool on) schaltet die im ersten Parameter angegebene LED abhängig vom Wert des zweiten Parameters ein oder aus. Auf sie greift ausschließlich die Methode roll() zu. Hier der komplette Code:

#ifndef Dice_h
#define Dice_h

// Simple class that defines a binary dice using 3 Bits
// with each of the Bits representing a digit of the binary number
// with a decimal value from 1..6
class Dice {
public:
Dice(int led4, int led2, int led1); // initialized with Pins
void roll(); // roll the dice
private:
int _led4; // LED representing bit 2
int _led2; // LED representing bit 1
int _led1; // LED representing bit 0
void controlLED(int led, bool on); // internal method to turn LED on/off
};
#endif

Die Datei Dice.cpp enthält die Implementierung der obigen Klasse. Im Konstruktor merkt sich die Klasse die Anschlusspins der LEDs (_led4, _led2, _led1). Die Pins werden über pinMode() als Ausgabepins deklariert. Die Methode randomSeed() dient zum Initialisieren des Zufallsgenerators. Mittels analogRead(A0) ergibt sich ein initialer Seed-Wert.

In der Methodenimplementierung von roll() lassen wir den Zufallsgenerator mittels Aufruf von random() eine Zahl von 1 bis 6 erzeugen. Durch simple Arithmetik berechnet die Methode roll() die einzelnen Stellen der entsprechenden Binärzahl und schaltet dementsprechend die jeweiligen LEDs.

Zu Debug-Zwecken können wir am Anfang der .cpp-Datei DEBUG definieren. Dadurch erfolgt in den Vereinbarungen

  #ifdef DEBUG
...
#endif

eine zusätzliche Ausgabe über den seriellen Monitor.

#include <Dice.h>
#include <Arduino.h>

#define DEBUG
Dice::Dice(int led4, int led2, int led1) {
_led4 = led4; // LED that represents MSBit
_led2 = led2; // LED that represents medium Bit
_led1 = led1; // LED that represents LSBit
pinMode(_led4, OUTPUT); // all pins are OUTPUT pins
pinMode(_led2, OUTPUT);
pinMode(_led1, OUTPUT);
randomSeed(analogRead(A0)); // random value from analog A0 for
// initial seed
}

void Dice::roll() {
int result = random(1,7); // get a random number from 1..6

#ifdef DEBUG
Serial.print("dice value = ");
Serial.println(result);
#endif

controlLED(_led4, (1 == result / 4 )); // simple arithmetic
#ifdef DEBUG
Serial.print("LED4 = ");
Serial.println(1 == result / 4);
#endif

result = result % 4;
controlLED(_led2, (1 == result / 2));

#ifdef DEBUG
Serial.print("LED2 = ");
Serial.println(1 == result / 2);
#endif

result = result % 2;
controlLED(_led1, 1 == result);

#ifdef DEBUG
Serial.print("LED1 = ");
Serial.println(1 == result);
#endif
}

void Dice::controlLED(int led, bool on) {
if (on)
digitalWrite(led, HIGH);
else
digitalWrite(led, LOW);
}

Ein simples Arduino-Beispielprogramm, das diese Bibliothek nutzt, könnte wie folgt aussehen:

#include "Dice.h"

// Dice with LEDs on digital Pins 9, 10, 11
Dice myDice(9,10,11);

void setup() {
Serial.begin(9600);
}

void loop() {
myDice.roll(); // roll the dice
delay(2000);
}

Zunächst ist die Headerdatei der Bibliothek zu inkludieren. Einzige Vereinbarung ist eine Instanz von Dice mit LEDs an den digitalen Anschlusspins 9, 10 und 11. In der Schleife (loop) wird alle zwei Sekunden gewürfelt: myDice.roll() .

Alle Beispiele müssen per Konvention in einem Unterverzeichnis der Bibliothek namens ./examples liegen. Jedes Beispiel xyz.ino wiederum liegt in einem Unterverzeichnis gleichen Namens: ./xyz. Dem Sketch habe ich den Namen DiceTest.ino gegeben, weshalb er im Unterverzeichnis ./examples/DiceTest auftauchen muss.

Die Implementierungsdateien der Bibliothek können im Basisverzeichnis dieser Bibliothek liegen oder im Unterverzeichnis ./src.

Zusätzlich zu den Implementierungsdateien existieren Dateien mit Metainformationen, die im Hauptverzeichnis der Bibliothek liegen müssen. Insgesamt schaut für das Beispiel die Verzeichnisstruktur wie folgt aus:

Das Verzeichnis für die Beispielsbibliothek unter Mac OS X

Die Datei keywords.txt enthält eine Liste von Schlüsselwörtern der Bibliothek, die der Syntaxeditor farbig kenntlich machen soll. Datentypen attributieren Programmierer mit KEYWORD1, Methoden mit KEYWORD2:

# Schlüsselwörter für Datentypen:
Dice KEYWORD1
# Schlüsselwörter für Methoden: KEYWORD2
roll KEYWORD2

Die Datei library.json beschreibt die Bibliothek mittels einer JSON-Datenstruktur:

{
"name": "DiceLibrary",
"frameworks": "Arduino",
"keywords": "dice, LED",
"description": "Implement a binary dice",
"authors":
[
{
"name": "Michael.Stal",
"email": "michael@stal.de",
"url": "http://www.stal.de",
"maintainer": true
},
{
"name": "Hans Hase"
},
{
"name": "Donald Duck",
"email": "duck@entenhausen.de"
}
],
"repository":
{
"type": "git",
"url": "https://github.com/ms1963/DiceLibArduino"
}
}

Daneben gibt es noch die Datei library.properties, die Informationen über die Bibliothek wie zum Beispiel deren Version oder die unterstützten Arduino-Plattformen enthält:

name=DiceLibrary
version=1.0.0
author=Michael Stal
maintainer=Michael Stal
sentence=Library to use three LEDs as a binary dice.
paragraph=
category=Sensors
url=www.stal.de
architectures=*

In die Datei README.md schreiben Sie wissenswerte Information über die Bibliothek, etwa einen Überblick ihrer Funktionalität und ihres Einsatzzwecks:

# DiceLibArduino
Just an example of what it takes to provide an Arduino library.
This is not meant to be a serious implementation.

In der Datei LICENSE kommt die Beschreibung der Open-Source-Lizenz, unter deren Obhut die Bibliothek stehen soll:

The MIT License (MIT)

Copyright (c) 2016 Michael Stal

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Da sich alle Arduino-Bibliotheken für gewöhnlich in GitHub befinden, bietet es sich an, einen eigenen GitHub-Account zu erstellen, und dort jede Bibliothek als öffentliches (oder privates) Repository zur Verfügung zu stellen. Das Ergebnis sieht dann aus der Benutzerbrille für das Beispiel wie folgt aus:

Die in diesem Artikel entwickelte Bibliothek finden Sie in GitHub unter: https://github.com/ms1963/DiceLibArduino.

Wollen Sie eine Bibliothek in Ihren eigenen Projekten nutzen, holen Sie sich die .zip-Datei von der GitHub-Seite und importieren Sie sie in der Arduino IDE über den Menüpfad Sketch | Include Library | Add .ZIP Library ... Danach finden Sie für das Beispielprojekt unter File | Examples | DiceLibrary den vorgestellten Beispielsketch DiceTest.

In dieser Minifolge ging es um den Eigenbau von Arduino-Bibliotheken. Nur durch das Engagement vieler Personen existiert für Arduino-Entwickler eine große Palette verfügbarer Bibliotheken als Open-Source-Artifakte, die das Leben im Arduino-Ökosystem komfortabel machen. Sollten Sie keine Berührungsängste mit C++ haben, versuchen Sie sich doch einmal an eigenen Bibliotheken. Die Community dankt es Ihnen.

Nach diesem kurzen Ausflug in die Welt der Bibliotheken setze ich die IoT/Arduino-Serie wie gewohnt fort.

Mehr Infos

()