ESP8266 & Telegram

Einen Mikrocontroller per Messenger Befehle senden oder Daten erhalten? Ja das geht, und es ist auch genau so cool wie es sich anhört!

An dieser Stelle sollte eigentlich ein kurzes Video folgen, da man dazu jedoch einen Premium-Tarif benötigt, und das neben meiner Website etwas unnötig wäre, gibt es stattdessen den Link zur genannten Website: 

http://deloarts.com/de/arduino/esp8266_telegram/

Dank der entsprechenden Lib (folgt später im Eintrag) erspart man sich viel Schreibarbeit und kann gleich loslegen. Wenn man sich die Bibliothek genauer anschaut merkt man die Ähnlichkeit zur Python API, es ist lediglich stark abgespeckt, was für die Nutzung auf dem ESP nur Hilfreich ist.

Telegram

Zuerst muss man Telegram als Messenger nutzen, entweder auf dem Smartphone, Tablet oder dem PC. Danach kann man schon damit beginnen, einen Bot zu erstellen. Der Übersicht zu Liebe folgt ein Copy/Paste-Inhalt aus dem Raspberry-Tutorial.

Startet damit in das Suchen-Feld den Namen BotFather einzugeben. Dies ist der Manager aller Bots auf Telegram. Bei ihm sucht man um einen neuen Bot an.

Search_Botfather

Sendet ihm den Befehl /newbot, wählt einen Namen und einen Benutzernamen. Der Benutzername ist öffentlich sichtbar und auffindbar, durch geschickte Programmierung redet der Bot aber nur mit ausgewählten Nutzern. Als Namen vergebe ich gerne einen projektbezogenen, in diesem Fall wäre ‚Doorbell‘ recht passend.

Talk_To_BotFather

Ist der Vorgang abgeschlossen, so erhält man einen eindeutigen Token. Dieser ist später im Programm wichtig!

Das Programm

Zu finden wie immer auf GitHub. Wer neu ist, und das ESP noch nicht so gut kennt, dem empfehle ich das Setup-Tutorial zum Board.

Oben im Beitrag habe ich die Lib erwähnt, welche für die Nutzung der Telegram API benötigt wird. Die habe ich sie in das GitHub-Verzeichnis von mir kopiert, um immer die passende Version zu haben.

Bevor ihr beginnen könnt, gebt die WLAN-Daten eures Netzwerks ein.

static char SSID[] = "your network name";
static char password[] = "your password";
static char hostname[] = "Doorbell";

Weiter geht’s mit den Definitionen des Programms, dazu muss man den Botnamen, seinen Usernamen, den Token und die ID angeben, an welche die Nachrichten geschickt werden sollen.

#define botName "Doorbell"
#define botUserName "something that ends with bot"
#define botToken "your:token"
#define adminID "yourID"

Die adminID ist demzufolge die ID eures persönlichen Telegram-Accounts. Um diese herauszufinden, ladet das Script auf den ESP, gebt aber zuvor in den Programmdefinitionen eure Daten ein (Username & Token). Über das Suchen-Feld in Telegram könnt ihr über den Usernamen euren Bot finden. Sendet danach dem Bot über Telegram eine Nachricht, dieser wird euch sodann eure ID zurücksenden.

String senderID = bot.message[i][4];
bot.sendMessage(bot.message[i][4], "Access denied. Your ID: " + senderID, "");

Ein anderer Weg ist der serielle Monitor. Die Chat ID wird bei jedem Zugriff angezeigt.

Tragt danach die ID einfach an der entsprechenden Stelle im Programm ein, und voilà, der Bot hört auf euch.

Am Beginn sollte man den ESP via USB mit dem PC verbinden (auf die Baudrate achten), um die Debuginformation zu erhalten. So lässt sich leicht ein möglicher Fehler entdecken, etwa falsche WLAN-Zugangsdaten oder ein falscher Token.

Zurück zum eigentlichen Programm.

Im setup() werden alle Routinen gestartet, das Programm ist (hoffentlich) gut auskommentiert.

In der loop() ist es schon etwas interessanter. Grundsätzlich wäre dem Zweck Genüge getan, wenn der Controller auf das Betätigen des Tasters wartet, und dann eine Nachricht an die AdminID sendet.

if (!digitalRead(inputPin))
{
    bot.sendMessage(adminID, "Ding Dong", "");
}

Ich möchte aber auch zeigen, wie man dem ESP über Telegram Befehle senden kann, weshalb das Programm noch etwas weiter geht. So könnte man etwa ein Relais ansteuern, oder Daten eines Sensors abgreifen. Um das tun zu können, muss man wissen, wo die Daten gespeichert werden.

Grundsätzlich werden die Telegram-Daten beim Empfangen in einem zweidimensionalen Array gespeichert: bot.message[i][j]. Ist i gleich 0, so werden Sonderfälle behandelt:

  • bot.message[0][0]: Anzahl der anstehenden Nachrichten.
  • bot.message[0][1]: ID der letzten Nachricht.

Ansonsten ist i der Index der eingegangenen Nachrichten. j beinhaltet die Daten, welche zu i gehören.

  • bot.message[i][0]: Update ID
  • bot.message[i][1]: User ID
  • bot.message[i][2]: Vorname
  • bot.message[i][3]: Nachname
  • bot.message[i][4]: User ID
  • bot.message[i][5]: Nachricht

Zuerst prüfen wir, ob Updates, also neue Nachrichten zur Verfügung stehen. Diese werfen wir dazu in das Array um sie später zur Verfügung zu haben.

bot.getUpdates(bot.message[0][1]);

Danach arbeiten wir uns Schritt für Schritt durch die Daten durch, und prüfen zugleich, ob die empfangene Nachricht einem Befehl entspricht.

for (int i = 1; i < bot.message[0][0].toInt() + 1; i++)
{
    if (bot.message[i][4] == adminID)
    {
        bot.message[i][5] = bot.message[i][5].substring(1, bot.message[i][5].length());
        if (bot.message[i][5] == "info")
        {
            bot.sendMessage(bot.message[i][4], "Hello.", "");
            bot.sendMessage(bot.message[i][4], "I am your doorbell.", "");
        }

        ...

    }
}

i entspricht, wie wir wissen, dem Index der IDs, welche dem Bot eine Nachricht gesendet haben. Bevor wir also auf eine Nachricht antworten, prüfen wir ob der Absender auch berechtigt ist, eine Antwort vom Bot zu bekommen. Ist er das nicht, bekommt er eine entsprechende Rückmeldung.

bot.sendMessage(bot.message[i][4], "Access denied.", "");

So viel zum Programm. Man sieht, es steckt nicht viel dahinter (also, eigentlich schon, aber das Interface ist einfach).

Der elektrische Aufbau

Zum Testen des Programms reicht die Lochrasterplatine vom Setup-Tutorial und ein Taster, wie es im Video am Beginn gezeigt wird.

Ich möchte die bestehende Türklingel nicht durch die WIFI-Klingel ersetzen, sondern beide parallel bestehen haben. Deshalb setze ich ein Relais in die Leitung der vorhandenen Glocke, welches das Signal an den Controller gibt, sobald die Taste betätigt wird. Hierfür muss man das Relais entsprechend der verbauten Klingel auswählen. Meine arbeitet mit 12VDC, weshalb ich auch ein Relais nutze, das bei 12V schließt. Dabei ist darauf zu achten, dass das Netzteil für die Klingel genügend Strom für die Klingel selbst, als auch für das Relais liefert.

shematic.jpg

Im Schaltplan erkennt man unter anderem, dass das Relais ohne Pull-Down-Widerstand (im Normalfall 10kΩ) an den Controller angeschlossen wird. Der Grund ist der gleiche, wie der im Setup-Tutorial beschriebene: Ist einer der beiden GPIO-Pins beim Booten nicht logisch HIGH (oder in einem Zwischenzustand), so startet der ESP nicht das Programm, sondern erwartet den Upload eines Programms. Deshalb starte ich den ESP mit offenem Relais (es ist doch sehr unwahrscheinlich, dass jemand anleutet wenn der Controller gerade bootet).

Der zweite Punkt, welcher auffällig ist, ist dass der GPIO-Pin über das Relais nicht mit +3V3 verbunden ist, sondern mit der Groundleitung. Dazu bedarf es einer Erklärung des …

INPUT_PULLUP

Von den Schaltungen eines Arduinos ist man es gewohnt, einen Taster mit VCC und dem Pin zu verbinden. Vom Pin ausgehend schließt man einen hochohmigen Widerstand auf Ground um Störsignale oder Signalschwankungen zu filtern.

input

Würde man den Pull-Down-Widerstand weglassen, so wäre der Pin bei offenem Taster in einem Zwischenzutand (floating). Es ist also nicht eindeutig festgelegt, ob der Taster nun offen ist. Auf diese Weise können Signale der Umgebung, elektromagnetische oder magnetische Felder, den Pin beeinflussen. Das kann man sich sehr gut veranschaulichen, indem man sich über den seriellen Monitor eines blanken Arduinos den Wert eines Analogpins ausgeben lässt. Bewegt man das Board oder hält ein elektrisches Gerät in seine Nähe, so ändert sich der Wert.

/* Button an Pin 12,
   LED an Pin 13
*/
pinMode(12, INPUT);
pinMode(13, OUTPUT);
digitalWrite(13, digitalRead(12));

Das Programm sieht daher so aus wie immer, wird der Taster betätigt, so leuchtet die LED. Das ganze kann man aber auch umgekehrt lösen, indem man einen Pull-Up-Widerstand nutzt. Dieser wird zwischen VCC und dem Pin eingesetzt.

input_pullup

Ist der Taster offen, so bleibt die Spannung am Widerstand gleich – 5V, 3V3, … was auch immer die Versorgungsspannung ist. Das bedeutet, dass am Pin immer 5V, 3V3, … anliegen. Wird der Taster gedrückt, so fällt die Spannung auf Null ab. Durch den hochohmigen Widerstand verhindert man zudem ein Kurzschließen des Schaltkreises. Man sollte allerdings keinen allzu großen Widerstand wählen, da ansonsten das Rauschen wieder zurückkehrt (Luft ist ja auch ein Widerstand) – 10kΩ sind die Faustregel bei Controllern bis +5V Betriebsspannung.

/* Button an Pin 12,
   LED an Pin 13
*/
pinMode(12, INPUT);
pinMode(13, OUTPUT);
digitalWrite(13, !digitalRead(12));

Im Programm hat man nur eine kleine Änderung zu machen: Den Input mit dem Rufzeichen zu negieren. Ist der Taster nicht gedrückt gibt digitalRead(pin); true zurück und vice versa.

Nun könnten wir das bereits nutzen, um den Button am ESP anzuschließen. Der Pin wäre ohne Interaktion immer logisch HIGH, wodurch der Controller problemlos booten kann. Allerdings hat der Controller die nette Eigenschaft, einen hochohmigen Widerstand hinter seinen GPIO-Pins verbaut zu haben. Das bedeutet, dass wir uns darum nicht mehr kümmern müssen und der Aufbau sich vereinfacht:

input_internal

Gut, etwas muss man schon beachten: Der interne Widerstand muss aktiviert werden. Dies erledigt man, indem man den Input nicht mit pinMode(PIN, INPUT);, sondern mit pinMode(PIN, INPUT_PULLUP); festlegt.

/* Button an Pin 12,
   LED an Pin 13
*/
pinMode(12, INPUT_PULLUP);
pinMode(13, OUTPUT);
digitalWrite(13, !digitalRead(12));

Das lässt sich übrigens auch am Arduino anwenden. Wer also gerade keinen Widerstand zur Hand, oder nicht genug Platz für einen hat, muss nicht verzweifeln: einfach INPUT_PULLUP nutzen!

Weiters sind, auf das Arduino Nano bezogen, auch die analogen Pins A0-A5 (oder 14-19) als digitaler Input nutzbar, und sie haben ebenso einen Pull-Up-Widerstand verbaut. Lediglich die beiden letzten analogen Inputs (A6 und A7 [oder 20 und 21]) haben keinerlei Schaltung und dienen ausschließlich als analoger Input.

Und weil wir bereits beim Arduino sind: Der digitale Pin 13 kann nicht als Pull-Up-Input genutzt werden, da auf den Arduino Boards eine LED mit einem 330Ω Widerstand verbaut ist. Diese verhindert die Funktion, da sie den Pin dauerhaft mit GND verbindet und somit der Pin immer geschalten werden würde.

Zurück zum Thema: Für den Aufbau habe ich die folgenden Bauteile genutzt:

.

Stk. Komponente Ausführung Beschreibung
1 ESP8266 ESP-01
1 Widerstand 10kΩ
1 Spannungsregler LD33V 12VDC -> 3V3DC
2 Elektrolytkondensator 10µF
1 Kermaikkondensator 0.1µF
1 Relais 12VDC Passend zur Klingel

.

Die Kondensatoren dienen zur Glättung der Spannung, ohne diese würde der Spannungswandler instabil werden und die Spannung am Ausgang würde stark schwanken – schlecht für den ESP. Das passiert, weil der verwendete Spannungswandler von seinem Aufbau her ein rückgekoppelter Verstärker ist.

setup

Eine weitere Möglichkeit, neben dem Taster, wäre die Nutzung eines Infrarot-Bewegungsmelders. Diesen könnte man sogar an den verbeleibenden Pin des ESP anschließen (auf die galvanische Trennung beim Booten sollte man achten).

Fragen? Kommentar!

Advertisements

I²C & Bit-Shifting

In letzter Zeit wurde ich häufiger über I²C, beziehungsweise dem damit oft einhergehenden Bit-Shifting gefragt. Konkret meine ich damit den Code der OLED-Lib und dem des Wii-Nunchuk. Beide Geräte unterstützen den I2C-Bus, welcher Thema dieses Eintrages ist. Als Antwort habe ich meistens eine kurze Erklärung, sowie den Verweis auf den Wikipedia-Eintrag zu I²C gegeben. Hier will ich aber etwas genauer darauf eingehen, wie man I²C und Bit-Shifting tatsächlich auf mehreren Controllern nutzt.

setup

(Sorry für das schlechte Bild, hatte im Moment nur meine Handykamera)

Entwickelt wurde der BUS in den 80ern von Phillips. Der genaue Name lautet Inter-Integrated Circuits Bus (IIC), woraus schnell die Abkürzung I²C (i squared c) wurde. Oftmals liest man auch die Abkürzung TWI, diese steht für Two-Wire-Interface. Die Grundzüge sind:

  • Jedes Gerät hat seine eigene 7-bit Adresse. Dies bedeutet es können bis zu 2^7=128 Geräte angeschlossen werden.
  • Eine genaue Taktgeschwindigkeit, um die Daten senden/empfangen zu können.
  • Zwei Leiter, SDA, welcher den Takt vorgibt, und SCL, die Datenleitung.
  • Einen Master, welcher den Takt vorgibt und den Bus administriert.
  • Mehrere Slaves, welche dem Master unterliegen.

Es kann auch mehrere Master geben, Zugriff auf den BUS hat aber immer nur einer, weshalb man sich bei der Verwendung mehrerer Master etwas überlegen muss, damit jeder darauf zugreifen kann – Stichwort: Multi-Master.

Der elektrische Aufbau

Für dieses Beispiel nutze ich die folgenden Bauteile:

Stk. Komponente Ausführung Beschreibung
1 Arduino UNO Master
2 Arduino Nano Slave
2 Widerstand 1kΩ I2C Pull-up
2 Widerstand 220Ω Vorwiderstand LED
1 Widerstand 10kΩ Taster Pull-down
1 Potentiometer 10kΩ
1 Taster

Der folgende Schaltplan zeigt, wie der Aufbau aussehen soll. Wichtig ist, dass man hier nur das Arduino UNO mit Spannung über den USB-Port, den VIN-Pin oder den 7-12V-Anschluss versorgt. Die beiden Nanos dürfen mit keiner externen Quelle versorgt werden. Der Grund ist, dass man mit diesem Aufbau die Schutzmaßnahmen, sowie die automatische Wahl der Versorgung der Boards umgeht. Des weiteren sollte man in diesem Aufbau an keinem der Boards zu viele zusätzliche Geräte anschließen, da alles über den +5V-Pin des UNOs versorgt wird. Der Aufbau ist für dieses Beispiel konzipiert.

shematic

Wer mehrere Geräte an die Powerline (+5V) anschließen will, muss eine zusätzliche 5V-Versorgung verbauen, welche genung Strom für alle Geräte liefert und außerdem in jedem Fall einen stabilen Output hat. In diesem Fall darf man auch das UNO nicht mehr extern mit Spannung versorgen. Will man das UNO (in diesem Fall den Master) dennoch mit einem USB-Gerät kommunizieren lassen, so muss man auf einen externen FTDI-Adapter zurückgreifen, wobei man ihn dann nur mit RX, TX & GND des Masters verbinden darf. Die Logik des Aufbaus folgt dann dem folgenden Schaltplan:

shematic_2

Die beiden Pull-up Widerstände werden gebraucht, weil der BUS LOW-active ist. Das bedeutet, dass die Daten nicht durch eine 1, sondern durch eine 0 dargestellt sind (ähnlich zu einem Bar-Code, wo die weißen Balken die Daten repräsentieren). Die meisten erhältlichen I2C-Geräte haben die Widerstände bereits verbaut, weshalb man sich darum keine Gedanken mehr machen muss.

Der Code

Wie immer findet man das Programm auf GitHub.

Dort findet man drei Verzeichnisse: Master, Slave_Reader und Slave_Writer. Die Idee meines Beispiels ist, dass der Master Daten vom Slave_Reader anfordert und diese dann dem Slave_Writer zur Verfügung stellt. Diese Daten sind der digitale Wert eines Taster und der analoge Wert eines Potentiometers. Der Ablauf sieht wie folgt aus:

  • Der Master stellt einen Request an den Slave_Reader.
  • Der Slave_Reader bekommt diesen, nimmt die Daten auf (digitalRead() & analogRead()), konvertiert diese und schickt sie dem Master.
  • Der Master konvertiert die Daten und schickt sie an den seriellen Monitor, damit man sie am PC lesen kann.
  • Als nächsten Schritt schickt der Master die unkonvertierten Daten an den Slave_Writer.
  • Dieser erhält die Daten, konvertiert sie und bringt zwei LEDs entsprechend zum leuchten.

Mit dem Wort „Konvertierung“ meine ich Bit-Shifting. Doch was ist das?

Bits & Bytes

Um zu vertstehen, warum wir Bit-Shifting benötigen, muss man wissen, dass der I²C-Bus maximal ein Byte größe Nachrichten versenden kann. Man kann zwar mehrere 1-byte-Nachrichten hintereinander schicken, doch eben nur nacheinander. Ein Byte ist acht Bits lang, und ein Bit repräsentiert den kleinsten möglichen Zustand auf einem Rechner (sei es nun ein Mikrocontroller oder PC).

Als binäre Zahl dargestellt ist ein Bit also entweder eine Null oder eine Eins. Als Variable auf dem Mikrocontroller wird ein Bit als bool-Variable dargestellt (1-0, true-false, HIGH-LOW). Ein Byte ist wie bereits bekannt eine Kette aus 8 Bits, die daraus resultierende höchste Zahl ist 255. Wem das Konzept der binären Zahlen fremd ist: Es folgt ein kurzes Beispiel:

Die Zahl 0 als Byte dargestellt wäre eine einfache 8-stellige Kette aus Nullen: 00000000. Sind alle Stellen mit Einsern belegt, so ergibt sich die Zahl 255, oder eben 11111111. Die Zahlen dazwischen ergeben sich über die Summe aus 1 & 0 mit der folgenden Logik:

2^7=128 2^6=64 2^5=32 2^4=16 2^3=8 2^2=4 2^1=2 2^0=1 Summe
0 0 0 0 0 1 1 1 7
1 0 1 0 1 0 1 0 170
1 1 1 1 0 0 0 0 240

Nun lässt sich auch erkennen, warum es binäre Zahlen heißt; das Produkt resultiert immer aus der Potenz mit Basis 2. Des weiteren ist das Bit rechts immer das mit dem niedrigsten Stellenwert (least-significant-bit lsb) und das Bit links mit dem höchsten (most-significant-bit msb). Auf diese Weise können alle Zahlen dargestellt werden, man benötigt lediglich genug Bits. Daher stammt unter anderem die Namensgebung des 64-Bit-Betriebssystems. Mit solch einem Prozessor können 64 Bits (8 Bytes) gleichzeitig berechnet werden. Als Zahl betrachtet sind das 2^64=18446744073709551616 mögliche Zustände.

Generell bedeutet das „B“ in msb und lsb jedoch Byte und nicht Bit.

Auf dem AVR-Controller, welcher auf dem Arduino verbaut ist, ist eine Integer-Variable 2 Bytes groß. Das muss man wissen, denn auf anderen Maschinen (PCs, etc.) ist ein Integer 4 Bytes groß. Man kann also folgendes festhalten:

byte a = 255;           // Von 0 bis 255, 1 Byte: 2^8=256
int b = 32767;          // Von -32767 bis 32767, 2 Bytes (1 Vorzeichenbit): 2^15=32768
unsigned int c = 65535; // Von 0 bis 65535, 2 Bytes: 2^16=65536
/*  
   Das erste (linke) Bit ist im Regelfall immer das
   Vorzeichen-Bit. Durch das Keyword 'unsigned' kann 
   dieses als Datenbit genutzt werden.
*/

Jetzt sieht man auch die Problematik: Mit dem I²C-Bus kann man wie bereits erwähnt nur 1-Byte lange Nachrichten senden. Einen Integer zu senden funktioniert nicht so einfach, wie man es gerne hätte, denn er ist einfach zu lang. Das führt uns zum eigentlichen Thema:

Bit-Shifting

Der Grundgedanke um das Problem zu lösen ist: Teile den 2-Byte großen Integer am Slave in zwei 1 Byte große Pakete auf, schicke diese nacheinader und setze sie am Master wieder zusammen. Klingt einfach, ist es auch.

Hierfür stellt uns der C++ Standard auch die nötigen Werkzeuge zur Verfügung. Wir brauchen die folgenden Operatoren:

<< Nach links shiften
>> Nach rechts shiften
& Bitweises UND
| Bitweises ODER

Zum besseren Verständnis folgt eine kurze Erklärung der Funktion der Operatoren.

Nach links shiften
110010011 << 2 = 001001100
110010011 << 4 = 100110000
110010011 << 6 = 011000000
/* 
   Die Bits werden um die nach dem '<<'-Operator angegeben Zahl
   nach links verschoben. Am rechten Ende werden Nullen angehängt.
*/
Nach rechts shiften
110010011 >> 2 = 001100100
110010011 >> 4 = 000011001
110010011 >> 6 = 000000110
/* 
   Die Bits werden um die nach dem '>>'-Operator angegeben Zahl
   nach rechts verschoben. Am linken Ende werden Nullen angehängt.
*/
Bitweises UND
1100 & 1010 = 1000
1111 & 0000 = 0000
1111 & 1010 = 1010
/* 
   Das bitweise UND gibt nur dann eine Eins, wenn zwei Einsen addiert werden.
   In jedem anderen Fall ist das Ergebnis Null.
*/
Bitweises ODER
1100 | 1010 = 1110
1111 | 0000 = 1111
1111 | 1010 = 1111
/* 
   Das bitweise ODER gibt nur dann eine Null, wenn beide Bits Null sind.
   In jedem anderen Fall ist das Ergebnis eine Eins.
*/

Mit diesem Wissen erklärt sich auch, was im Programm des Slave_Reader passiert. Dort findet man die folgenden Zeilen:

byte msb = (integerValue >> 8) & 0xFF;
byte lsb = (integerValue) & 0xFF;

Das most-significant-byte ist das linke der beiden. Ich rücke also den Integer bitweise um 8 Stellen nach rechts, wodurch die nun linken 8 Bits Nullen sind. Weil man nun trotz allem noch 16 Bits hat UNDe ich bitweise mit 0xFF (binär: 11111111). Auf diese Weise fallen die ersten 8 Bits weg. Im nächsten Schritt widme ich mich dem least-significant-byte – dem rechten Byte. Hier reicht es, einfach die linken 8 Bits zu löschen. Dies erledigt man wieder mit dem bitweisen UND von 0xFF.

Dem ’schöneren‘ Aussehen des Programms wegen schreibe ich die zu sendenden Bytes in ein Byte-Array. Man muss darauf achten, dass dieses Array am Master als auch am Slave die gleichen Daten an den gleichen Indizes hat. Also Integer_A an Stelle 0 & 1, Integer_B an Stelle 2 & 3, usw. Beim tatsächlichen Senden der Daten mit Wire.write(); steht die Zahl für die Größe des Arrays.

byte buffer[3] = {0};
buffer[0] = digitalValue; // Boolsche Variable, benötigt nur 1 Bit Platz
buffer[1] = (analogValue >> 8) & 0xFF; // analogValue ist ein Integer
buffer[2] = (analogValue) & 0xFF;
Wire.write(buffer, 3);

Betrachtet man nun den Code vom Master, so findet man die Zeile

Wire.requestFrom(slaveReader, 3);

, welche die Funktion requestHandler() am Slave_Reader triggert. Die Zahl 3 steht wieder für die Größe des Pakets in Bytes. Der Master fordert also den Slave auf, ihm Daten zu senden. Hat der Slave das getan, so werden die Daten mit den folgenden Zeilen gelesen.

while(Wire.available())
    for (int i = 0; i < 3; i++)
        buffer[i] = Wire.read();

Nun muss man lediglich die zwei Bytes des Integers wieder zusammenfügen. Dies erledigt man kurz und bündig in einer Zeile:

analogValue = (buffer[1] << 8) | buffer[2];

Was passiert hier? Das most-significant-byte befindet sich im Array an der Stelle 1, das lsb an Stelle 2. Deshalb schiebe ich das msb um 8 Stellen nach rechts, erschaffe dadurch eine 16-Bit Binärzahl in der sich an den rechten 8 Stellen lauter Nullen befinden. Diese überschreibe ich mit dem bitweisen ODER mit dem lsb. É voilà, der Integer ist wieder ganz.

Nachdem der Integer am seriellen Monitor ausgegeben wurde gehen wir noch etwas weiter und senden das Array an den Slave_Writer. Durch die drei Zeilen


Wire.beginTransmission(slaveWriter);
Wire.write(buffer, 3);
Wire.endTransmission();
    

im Programm des Masters wird die Verbindung zum Slave hergestellt, es wird das Paket gesendet, und die Verbindung wird getrennt. Die Funktion receiveHandler() im Sourcecode des Slave_Writers wird daduch getriggert und beginnt die Daten zu lesen. Über das gleiche Bit-Shift-Verfahren wie im Master wird der Integer wieder hergestellt. Letztendlich werden die beiden LEDs entsprechend angesteuert.

Die Variable slaveWriter beinhaltet lediglich die hexadezimale Zahl 0x02, welche in Dezimalschreibweise für 2 steht. Kurz: Der Slave_Reader hat die Adresse 0x01 = 1, der Slave_Writer die Adresse 0x02 = 2. Die Adressen können frei gewählt werden.

Letzte Worte

Geräte, welche an dem I²C Bus angeschlossen sind müssen immer mit Spannung versorgt sein. Ein deaktiviertes Gerät sollte deshalb auch nicht am Bus angeschlossen sein. Der Grund ist, dass das Gerät dann Spannung aus den beiden Busleitungen bezieht. Das Resultat ist ein blockierter Bus oder ein defektes Gerät.

Ich habe versucht, alles relevante zu beschreiben, was man wissen muss, um Daten mit dem I²C Bus zu senden. Falls dennoch Fragen offen sind: Ein Kommentar oder die Doku.

GPS w/ uBlox neo6mv2

Zu wissen, wie man GPS Rohdaten von einem entsprechenden Modul ausliest, und diese dann auch verarbeiten kann, kann in vielen Situationen von Vorteil sein. Verwendung findet ein GPS Modul in Multicoptern, Tracker oder um Daten an bestimmten Orten zu sammeln, um nur ein paar Beispiele zu nennen. Interessant wird es, wenn man GPS Daten im Zusammenhang mit anderen Sensordaten verwertet. Etwa die Beschleunigungswerte, gemessen durch eine entsprechende Sensorik, eines Kraftfahrzeuges.

setup

Die Funktionsweise

GPS ist ein Akronym für Global Positioning System. Um Weltweit die Position eines Gerätes bestimmen zu können, benötigt man nicht nur eben dieses Gerät, sondern auch Satelliten.

Entwickelt wurde das GPS vom US-Militär, bevor es seit den 1990er Jahren auch für zivile Zwecke eingesetzt wird. Der Vorteil dieser Positionsbestimmung ist, dass GPS Signal nur empfangen werden können, jedoch nicht gesendet werden. So kann man navigieren, ohne dass es Gegenspieler mitbekommen.

Die Satelliten, welche sich mit einer Relativgeschwindigkeit von etwa 3.9km/s zur Erde bewegen senden ein codiertes Radiosignal aus, welches ihre Umlaufbahnparameter und die Sendezeit beinhält. Dieses wird vom Empfänger verarbeitet, woraus dann die Position auf der Erde errechnet wird. Für eine genaue Bestimmung der Position benötigt man mindestens drei Satelliten (3 Punkte im Raum bestimmen die Position theoretisch exakt). Da die Empfängergeräte jedoch keine hochgenaue Uhr verbaut haben, wird mindestens ein weiterer Satellit benötigt, um die Uhrzeit genau bestimmen zu können.

Weitere Daten, etwa Richtungsangaben (Kompass) oder die Geschwindigkeit können über den Dopplereffekt bestimmt werden. Dabei ist die Signalverzerrung die ausschlaggebende Größe, welche ein Maß für die Daten ist.

Für mehr Informationen zum GPS: Wikipedia.

uBlox Neo6m v2

Die Überschrift lässt erahnen, welches Modul ich nutze. Es ist im Vergleich zu anderen GPS Receivern eines der günstigsten, lässt dennoch in den unterschiedlichen GPS-Modes eine Genauigkeit von bis zu 2.5 Meter laut Hersteller zu. Die GPS-Daten habe ich mit einem professionellem GPS-Gerät bei schönem Wetter auf einer Wiese abgeglichen, und bin zu dem Ergebnis gekommen, dass der eine Meter nicht unbedingt eingehaten wird. Maximal ergab sich eine Abweichung von etwa 6 Metern. Für den Preis von unter 10€ eine beträchtliche Leistung.

module

Die eigentliche Bezeichnung des Moduls lautet GY-GPS6MV2, NEO-6M bezieht sich lediglich auf den Chip, und nicht auf die gesamte Platine. Der NEO-6M Chip arbeitet mit einer maximalen Spannung von 3.6V, da sich auf der Platine aber ein Spannungswandler befindet, darf man den VCC-Pin mit 5V versorgen. Wichtig ist, dass die Signal-Pins (UART) auf keinen Fall mit mehr als 3.6V in Berührung kommen dürfen, denn es befindet sich kein Levelshifterauf dem Board.

Um das Modul dennoch mit einem 5V-Arduino nutzen zu können bedarf es entweder einem Levelshifter-Modul oder man stellt diesen durch einen einfachen Widerstands-Spannungsteiler her. Wie man das macht folgt etwas weiter unten. Laut Datenblatt sollte das GPS-Modul auch funktionieren, wenn man an VCC nur 3.3V (3.6V) anlegt, doch in diesem Fall habe ich nie verwertbare Daten vom Modul erhalten.

Der Aufbau

Wie bereits erwähnt, benötigt man für den Aufbau neben dem Controller und dem GPS Modul einen 2.2kΩ Widerstand und einen 1.0kΩ Widerstand.

shematic

Die Verkabelung erfolgt nach folgender Tabelle:

Arduino GPS LCD
+5V Vcc Vcc
GND GND GND
A4 SDA
A5 SCL
3 RX (mit Levelshifter)
4 TX

Der Grund, warum der TX-Pin (Transmit) keinen Widerstand benötigt ist, weil dieser das Signal an das Arduino sendet. Gegensätzlich dazu gibt das Arduino +5V an Pin 3 aus. Diese Spannung würde dem GPS nicht sonderlich gut tun, deshalb muss sie auf das logische Level des Moduls herabgesetzt werden.

Liquid Crystal Display via I2C

Bei dem Display handelt es sich um das sehr weit verbreitete HD44780 Liquid Crystal Display. Ihm wird lediglich ein Rucksack-Modul verpasst, welches die I2C-Befehle in HD44780er-Befehle umwandelt. Optional kann man das Display auch weglassen, das Programm muss man dafür nicht verändern. Wer will, kann die überflüssigen Zeilen aber auch löschen.

Display

Das Display-Modul nutzt wie bereits erwähnt den I2C-Bus. Er wird genau wie der letzte von mir vorgestellte Display angeschlossen:

  • SDA: A4
  • SCL: A5
  • Vcc: +5V
  • GND: Ground

Backpack

Der Code

Das Programm befindet sich auf GitHub.

Im Verzeichnis gps_basic findet man das Programm, welches die Grundidee beinhaltet. Das zweite, gps_tracker, behandle ich etwas später. Um die Vorgänge im Programm zu verstehen, muss man zuerst den Aufbau des Protokolls kennen, das vom GPS an das Arduino seriell gesendet wird. Im Ordner lib findet man die von mir verwendeten und funktionierenden Programmbibliotheken.

Zur Bestimmung der Position nutze ich die Daten des $GPGLL-Protokolls, und um zu erfahren, mit wie vielen Satelliten das GPS im Moment verbunden ist lese ich die Daten des $GPGSA-Protokolls.

$GPGLL

Dieses Protokoll sendet die geografische Position (Längen- und Breitengrade) und die Zeit. Der Aufbau sieht mit Beispielswerten wie folgt aus:

$GPGLL,4704.46496,N,01241.63682,E,110152.00,A,D*29

Man erkennt, dass die Daten mit einem Komma getrennt sind, was der Idee des csv-Formatsentspricht. Die Rohdaten ergeben nach der Verarbeitung folgende Nutzdaten:

  • 4704.46496,N: 47° 04.46496′ Nord
  • 01241.63682,E: 12° 24.63682′ Ost
  • 110152.00: Uhrzeit nach UTC+00
  • A: Daten validiert.
  • D*29: Checksumme

Die Koordinaten werden im Format Grad & Dezimalminuten ausgegeben. Wem geografische Kooridnaten nichts oder nur wenig sagen: Wikipedia. Grundsätzlich gilt aber, dass die Erde in Kugelkooridinaten mit Längengrade (Longitude) und Breitengrade (Latitude) unterteilt wird. Die geografische Länge hat eine Spannweite von -180° (Westen) bis + 180° (Osten), ausgehend von Greenwich in Großbritanien. Die geografische Breite reicht von -90° (Süden) bis +90° (Norden). Die Hauptachse ist in diesem Fall der Äquator.

$GPGSA

Dieses Protokoll enthält Daten über den GPS-Mode, die Anzahl der aktiv verbundenen Satelliten und über die Verringerung der Genauigkeit (Dilution of Precision [DOP]).

$GPGSA,A,3,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,ID,1.7,1.1,1.3*55

  • A,3: Automatische Wahl, 3D
  • ID: ID des Satelliten (11 Satelliten maximal)
  • 1.7: PDOP
  • 1.1: HDOP
  • 1.3: VDOP
  • *55: Checksumme

Eine Liste aller GPS-NMEA Sätze gibt es hier.

Data Parser

Will man die Daten vom GPS einfach nur über das Arduino zum seriellen Monitor durchschleusen, so genügt es am Beginn des Programms die Kommentar-Slashes der Zeile 13 zu entfernen.

#define DATA_PARSER

Da dieser Task aber sehr langweilig ist, gehen wir gleich weiter zum eigentlichen Code.

Serielle Daten aufnehmen

In der loop() wird gewartet, bis serielle Daten vom GPS (gps ist hier eine Instanz von SoftwareSerial) verfügbar sind. Sobald das Dollarzeichen auftaucht werden die Daten in einen Buffer geschrieben. Taucht das Sternsymbol auf, welches Teil der Checksumme ist, wird das Lesen beendet und die Daten werden zur Weiterverabeitung – processRawData() – frei gegeben.

while (gps.available())
{
	GPSRX = gps.read();

	if (GPSRX == '$')
	{
		RXbuffer = F("$");
		enableRead = true;
	}
	else if (GPSRX == '*')
	{
		processRawData(RXbuffer);
		enableRead = false;
	}
	else if (enableRead)
	{
		RXbuffer += GPSRX;
	}
}
Datenverarbeitung

In der Funktion processRawData() wird zuerst geprüft, ob die Daten des $GPGLL-Protokolls validiert sind. Ist dies der Fall, so wird der Inhalt über splitString() aufgeteilt. Da die Grade jedoch in einem Strang mit den Minuten kommen muss man eine kleine Division einfügen. Hier nutze ich die Eigenschaften von Integer- und Floatvariablen (Gleitkommazahlen löschen).

buffer = (splitString(splitString(data, ',', 1), '.', 0)).toInt();
latitude.degrees = buffer / 100;
latitude.minutes = (buffer - latitude.degrees * 100) + splitString(splitString(data, ',', 1), '.', 1).toFloat() / 100000;

Die selbe Vorgehensweise habe ich auch bei den Breitengraden, der Zeit und dem anderen Protokoll, allerdings mit den angepassten Positionen im Protokoll.

Das war bereits alles, was es zu verarbeiten gibt. So einfach kann man GPS-Daten für sich nutzen.

User Interface

Die Aktualisierungsgeschwindigkeit der Daten sowohl am Display als auch am seriellen Monitor wird vom GPS vorgegeben. Das Modul hat eine maximale Aktualisierungsrate von 1Hz, und diese Zeit nutze ich auch.

In der setUI()-Funktion werden die Daten auf ein einheitliches Format gebracht, sodass sie immer gleich am Display und im seriellen Monitor dargestellt werden. Zusätzlich hat man am Beginn des Programms die Wahl, ob man die Daten seriell im csv-Format ausgeben will. Dazu genügt es die Definitionen entsprechend auszukommentieren.

//#define CSV
//#define CSV_MS_EXCEL
#define NO_CSV

Der Unterschied zwischen ’normalen‘ csv und MS Excel csv ist, dass Excel die Daten mit Strichpunkten anstatt einem Beistrich trennt, und der Dezimalpunkt kein Punkt, sondern ein Komma ist. Das folgende Bild zeigt die drei möglichen Ausgaben.

ui_modes

Die Anzahl der Satelliten und die Warnung der nicht-validierten Daten wird nur im NO_CSV Modus ausgegeben.

GPS Tracker

Da jetzt klar ist was dahinter steckt, kann man einen Schritt weiter gehen und sich mit Hilfe eines SD-Kartenmoduls einen GPS-Tracker erstellen. Den Display habe ich komplett weggelassen, da es mir in erster Linie darum geht, Geodaten während dem Gehen zu sammeln. Der Display würde hier nur unnötig Energie verbrauchen.

gps_tracker

Als SD-Kartenleser kommt das Modul von Catalex zum Einsatz, welches ich bereits hierbeschrieben habe, allerdings nutze ich es in diesem Tutorial mit dem ’normalen‘ SPI, und nicht mit SoftSPI. Der Grund ist, dass ich kein weiteres SPI-Gerät nutze, das durch das SD-Modul gestört werden könnte. Für mehr Infos dazu: Einfach den zugehörigen Beitrag lesen.

sd_module

Der elektrische Aufbau

Im Grunde sieht der Aufbau aus wie oben, lediglich mit LCD– und Card Reader++. Die Verbindung der Pins folgt der Tabelle:

Arduino GPS Card Reader
+5V Vcc Vcc
GND GND GND
3 RX (mit Levelshifter)
4 TX
10 CS
11 MOSI
12 MISO
13 SCK

shematic_tracker

Der Code

Das Programm liegt im Verzeichnis gps_tracker auf GitHub. Die Grundbausteine sind gleich geblieben, ich habe es lediglich um die Funktionen der SD Karte erweitert. Das arduino schreibt nun immer wenn das GPS Daten schreibt diese auf die Karte und zeigt sie auch, optional, am seriellen Monitor an.

File csvFile = SD.open(F("log.csv"), FILE_WRITE);
csvFile.println(csvData);
csvFile.close();

Sind die Daten fehlerhaft oder ist keine Satellitenverbindung vorhanden, so werden keine Daten geschrieben. Dies ist etwas nachteilig, da man nicht weis, ob das Programm auch ordnungsgemäß ausgeführt wird. Man könnte auch hier eine LED verbauen, die anzeigt, ob die Daten validiert wurden, oder ob eine Verbindung zu den Satelliten besteht. Aber mein Tutorial soll ja auch nur als Denkanstoß dienen. Ich würde mich freuen, wenn coole Lösungen für diverse Probleme auftauchen.

Nur noch eine kleine Anmerkung: Wenn man zusätzlich Sensordaten senden will, dann erkennt man schnell die Vorteile des csv-Dateiformats. Man hängt diese einfach hinten an.

Falls sonst noch Fragen aufgetaucht sind, der Code ist gut auskommentiert und ein Kommentar ist immer erwünscht.

 

Servo Motor

Ich habe bereits gezeigt, wie ein Schrittmotor am Arduino genutzt wird. Nun will ich zeigen, wie man einen Servomotor steuern kann, und was dahinter steckt.

MG995

Ich nutze meistens den MG995 Servomotor, da er im Vergleich zu anderen Servomotoren mit einem Drehmoment von ~1,47Nm bei 6V viel bietet. Die Betriebsspannung liegt im Bereich zwischen 4,8-7,2 Volt, ich bewege mich eher im Bereich um 6V, das rührt aber eher von der Verwendeung von Batterien. Er hat zudem ein Getriebe aus Stahl und ist zweifach Kugelgelagert (Festlager abtriebsseitig). Der Motor ist zwar teuerer, dafür ist er als Stoßfest deklariert und seine Lebensdauer ist weit höher, als bei Motoren mit Kunststoffgetrieben. Er hat einen Rotationsbereich von 180° (+- 90° vom Nullpunkt), und eine maximale Stellgeschwindigkeit von 0.24 s/90° bei 6V (ohne Last).

mg995.jpg

Der MG955 darf nicht über das Arduino (oder einen anderen Controller) mit Spannung versorgt werden, da er mehr Strom benötigt, als der Spannungswandler oder ein Output-Pin des Arduinos zur Verfügung stellen kann – der Wandler würde überhitzen. Deshalb nutze ich, wie zuvor kurz erwähnt, Batterien, Akkus oder ein Netzteil. Der Controller dient nur als Steuerung für den Motor.

Die Funktionsweise

Der Servoantrieb besteht aus einem Motor (Servomotor) und einer Regelelektronik. Der Motor kann dabei als Gleichstrommotor, Synchron- oder Asynchronmotor ausgeführt sein. Die Regelung dahinter ist in vielen Anwendungen eine Positionsregelung für den Winkel, sie kann aber auch als Geschwindigkeits- oder Momentenregelung ausgeführt sein, oder eine Kombination aus den genannten.

Wer sich den Inhalt zum Schrittmotor durchgelesen hat, und dann den Schrittmotor mit einen Drehgeber und einer Regelung ausstatten würde, der hätte sich so selbst einen Servomotor gebaut. Oft werden aber Servomotoren mit Servos, wie auch der MG995 einer ist, gleichgesetzt. Der Unterschied ist, dass ein ‚Servo‘ lediglich eine Form eines Servomotors ist.

Die Regelung des Servos (ich beziehe mich jetzt auf den MG995 und ähnliche Modelle) erfolgt über den Vergleich zwischen Soll-Wert, also jenem Wert, welcher vom Mikrocontroller vorgegeben wird, und dem Ist-Wert. Der Ist-Wert wird über ein integriertes Potentiometer ermittelt, welches ebenso nur eine Form von Drehgeber ist. Die Elektronik im Servo erledigt den Rest und stellt die Abtriebswelle über das Getriebe auf die gewünschte Position.

pulse

Die obige Grafik zeigt die, wie der Ist-Wert am MG995, oder auch auf anderen gängigen Servos, eingestellt wird. Es ist ersichtlich, dass der Wert über ein 50Hz-Signal eingestellt wird. Für die tatsächliche Positionierung des Winkels wird die Dauer der logisch High und der logsich Low Phase gemessen. Die Grafik macht deutlich, dass 1ms High in der Periodendauer von 20ms einen Winkel von bedeutet. 2ms High korreliert mit 180°.

Kurzer Einfwurf: Ich bin mir nicht sicher, ob das Datenblatt des MG995 einen Fehler hat, oder ob ich doch einen anderen Servo vor mir liegen habe, denn laut Datenblatt sollte dieser nur 120° weit drehen, doch er dreht volle 180°.

Der elektrische Aufbau

Viel bedarf es nicht, lediglich der folgenden paar Bauteile.

Stk. Komponente Ausführung Beschreibung
1 Servo MG995 Antrieb
1 Arduino UNO/Nano Controller
1 Batterie oder Netzteil 6V Versorgung
n Drahtbrücken

Das sind jene Komponenten, um den Servo mit allem zu versorgen, was er benötigt. Das Arduino muss selbst noch mit Spannung versorgt werden. Dies kann entweder über USB oder über den Power-Plug erfolgen.

components

Das Signal, also der Puls, kommt von einem der PWM-Anschlüsse des Boards, dieser kann frei gewählt werden. Neue Servos werden via PPM angesteuert. Wo genau der Unterschied liegt, erkläre etwas weiter unten.

basic_shematic

Sowohl der Ground vom Controller, als auch vom Servo und der Batterien müssen miteinander verbunden sein, da ansonsten die Signalleitung kein gemeinsames Potential hat – der Schaltkreis wäre nicht geschlossen.

Pulsweitenmodulation PWM

Der Grundgedanke war, ein analoges Signal aus einer digitalen Quelle zu bekommen – mit Digital-Analog Umsetzern. Je „schmäler“ der Puls, desto weniger Energie wird über eine Periodendauer frei, die Spannung am Ausgang ist niedrig, und vice versa. Der Grund ist, dass der Controller keine bestimmten Analogwerte am Ausgang liefern kann, sondern nur seine Operationsspannung (hier +5V).

pwm

Beim Servomotor wird allerdings kein analoges Signal genutzt. Die Pulsweite repräsentiert den Winkel am Motor (Beispiel von oben: 1.5ms = 90°). Man sieht, dass die Pulsweitenmodulation also auch Anwendung in der Steuerungstechnik findet. Ein weiteres Beispiel für eine steuerungstechnische Anwendung ist eine LED am PWM Ausgang. Diese wird schnell ein- und ausgeschalten, sodass das Auge dies als konstante Helligkeit wahrnimmt. Je schneller das passiert, desto geringer ist die reusltierende Helligkeit – so kann man das Licht einfach dimmen.

Pulsphasenmodulation PPM

PPM ist eine Erweiterung des PWM-Signals. Dabei werden hintereinander mehrere Pulse in der gleichen 20ms-Periode gesendet. Jeder Puls ist für einen anderen Servo zuständig. Der Vorteil ist klar: Mehrere Motoren können mit nur einer Signalleitung angesteuert werden, ohne dabei Zeit zu verlieren, denn die 20ms-Periode ist ohnehin auch beim „einfachen“ PWM für diese Motoren vorgegeben.

ppm

Der Code

Diesmal gibt es kein GitHub-Repu hierzu, da das Programm sehr kurz ist. Im Grunde zeige ich hier nur die Zusammenfassung des oben beschriebenen Inhalts. Ich zeige auch kein vollständiges Programm, da die Programmbibliothek von Arduino meines Erachtens nach sehr gut ist. Hier findet sich lediglich ein Einblick in den Grundgedanken hinter den Servos.

#define MinAngle 0
#define MaxAngle 180

#define POT 20000
#define MinPWM 1000
#define MaxPWM 2000

#define Multiplier (MaxPWM - MinPWM) / MaxAngle

void setup()
{
    ...
}

void loop()
{
    ...
}
 
void setServo (int Angle)
{
    int PWM = (Angle * Multiplier) + MinPWM;
    PORTD |= _BV(PORTD3);
    delayMicroseconds(PWM);
    PORTD &= ~_BV(PORTD3);
    delayMicroseconds(POT - PWM);
}

Die Definitionen zu Beginn spiegeln lediglich die Eigenschaften des Servos wieder. Der maximale Winkel, den der Servo befahren kann ist zugleich der längste Puls. MaxAngle in Grad entpricht also MaxPWM in Mikrosekunden, ebenso die Min-Werte. Die Periodendauer (Period Of Time [POT]) erhält man ebenso aus dem Datenblatt, bei den meisten Servos ist diese aber 20ms, oder 20000µs.

Der Multiplier ist jener Wert, welcher den gegebenen Winkel zufolge der gegebenen Werte so umrechnet, dass – bei diesem Servotyp – ein Winkel von einem PWM-Signal von 1000ms, und ein Winkel von 180° einem PWM-Signal von 2000ms entspricht. Da die Umrechung keiner Ganzzahldivision entspricht, ist diese mit einem Fehler behaftet.

In der setServo()-Funktion wird zuerst die Dauer des PWM-Signals ermittelt, danach wird für diese Dauer der Ausgang (Pin 3) des Controllers auf HIGH gesetzt. Danach wird die restliche Periodendauer das Signal auf LOW gehalten. Den Zugriff auf den Pin erledige ich nicht mit digitalWrite(Pin, State);, sondern mit dem C-Befehl. Mehr dazu findet man hier.

Durch das Ausführen des restlichen Codes ergibt sich eine Zeitspanne, in welcher die resultierende Periodendauer abweicht. In diesem kurzen Script spielt dies keine tragende Rolle. Sollte das Programm aber länger werden, oder gar Zeitverögernde Funktionen beinhalten, so würde die Periodendauer für die Steuerung des Servos stark abweichen. Abhilfe würde ein Timer schaffen, welcher alle 20ms das PWM-Signal über einen Interrupt triggert.

Diese Grundlogik, und weitere controllerspezifische Absicherungen sind übrigens in der Servo-Library von Arduino enthalten.

Noch eine Anmerkung zum Einschalten des Servos: Wer will, der kann die letzte Position des Servos im EEPROM des Arduinos speichern, sodass er beim Einschalten die Position hält. Das stromlos Schalten des Servos ist im Allgemeinen nicht notwendig, da die Regelung im positionierten Zustand nur wenige Milliampere benötigt.

Das war’s auch schon, ich hoffe, die Idee hinter Servos ist nun klar!

Arduino & ATtiny85

In diesem kurzen Beitrag zeige ich, wie man das Arduino als Programmer für den ATtiny85 Mikrocontroller nutzt.

Preview

Arduino IDE vorbereiten

Benötigt wird hierfür zunächst nur das Arduino Uno.

Arduino als ISP konfigurieren.

Als nächstes Öffnet man ArduinoISP (File, Examples, ArduinoISP) und lädt dieses Programm auf das Arduino Uno. Achtung, als Board muss hier das Arduino Uno ausgewählt sein.

ATtiny als Board hinzufügen.

Damit man das Arduino als ISP nutzen kann muss man zunächst den ATtiny in die IDE importieren. Dazu öffnet man die Preferences (File, Preferences) und klickt auf das Icon ganz rechts neben Additional Boards Manager URLs.

Preferences.JPG

Daraufhin öffnet sich ein weiteres Fenster, in welches man folgenden Link kopieren muss.

http://drazzy.com/package_drazzy.com_index.json

Additional_Boards.JPG

Im Anschluss öffnet man den Board Manager 

Boardmanager0.JPG

… und fügt das nötige Modul hinzu. In diesem Fall das ATtinyCore by Spence Konde.

Boardmanager1.JPG

Ist das erledigt kann man das ATtiny im Board Manager auswählen …

Boardmanager2.JPG

… und es im Anschluss konfigurieren.

Boardmanager3.JPG

Man muss darauf achten, alle Einstellungen mit meinen abzugleichen. Also den Timer auf CPU, den Chip auf ATtiny85 und die Clock auf 8MHz setzen.

Danach stellt man sicher, dass der Programmer auf Arduino als ISP eingestellt ist.

Arduino_As_ISP.JPG

Der Setup

Als nächstes folgt der physische Aufbau. Dazu benötigt man die folgenden Bauteile:

  • 1 Arduino Uno
  • 1 ATtiny85
  • 1 10µF Kondensator

Die Pins des ATtiny kann man mit Hilfe der folgenden Abbildung und der Wiring-Tabelle mit dem Arduino Uno verbinden.

ATtiny85_Pinout.png

  • Vcc: +5V
  • GND: GND
  • Reset: Pin 10
  • Pin 0: Pin 11
  • Pin 1: Pin 12
  • Pin 2: Pin 13

ATtiny85_Steckplatine.png

Burn Bootloader

Nun kann man das Arduino wieder mit dem PC verbinden und den Bootloader für den ATtiny auf diesen schreiben.

Burn_Bootloader.JPG

Nun kann man den ATtiny mit der Arduino IDE programmieren.

Programme hochladen

Als kleines Beispiel habe ich ein Programm vorbereitet, das eine LED blinken lässt. Abhängig von dem Status eines Tasters ändert sich die Blinkgeschwindigkeit. Der Aufbau muss dabei um

  • 1 LED
  • 1 220Ω Widerstand für die LED
  • 1 Pushbutton
  • 1 10kΩ Widerstand für den Button

ergänzt werden.

ATtiny85_Steckplatine1.png

Als nächsten Schritt fügt man das folgende Programm in die Arduino IDE ein und lädt es auf das ATtiny85 via dem Arduino Uno hoch.

#define LED 0
#define BUTTON 1

word Delay = 1000;

void setup() 
{
 pinMode(LED, OUTPUT);
 pinMode(BUTTON, INPUT);
}

void loop() 
{
 digitalRead(BUTTON) ? Delay = 250 : Delay = 1000;
 digitalWrite(0, HIGH);
 delay(Delay);
 digitalWrite(0, LOW);
 delay(Delay);
}

Das ganze sieht fertig dann etwa so aus:

Setup.jpg

Zum Schluss kann man noch alle Kabel, die zum Arduino führen, und den Kondensator entfernen, und eine Spannungsversorgung im Bereich von 3V bis 5V herstellen. Die 3V erreicht man beispielsweise durch 2 Batterien des Typs AA. Dann muss man allerdings den 220Ω Widerstand durch eine Drahtbrücke ersetzen.

Bei Fragen dazu, oder falls etwas nicht so funktioniert, wie es soll: Ein Kommentar ist immer erwünscht!

 

 

Water Drop (Pro) – Sensor Add-On

Wie ich es bereits im Water Drop (Pro) Beitrag (diesen sollte man zuerst lesen) angekündigt habe folgt hier das Sensor Add-On. Da die Hardware der Pro-Version bereits für einen Sensor ausgelegt ist, waren nur noch kleinere Änderungen in der Software zu erledigen und ein funktionierender Sensor zu bauen. Wer den Beitrag über die Advanced-Version gelesen hat, der wird bereits einen Verdacht haben, was nun folgt. Die Idee des Sensors beruht nämlich auf der selben wie in der älteren Version der Water Drop Reihe – einer Laserlichtschranke.

Um auch alles Zusatzmaterial auf einmal zu bekommen, habe ich das Projektverzeichnis auf dem Webserver aktualisiert. Die neuen Dateien können hier heruntergeladen werden.

Der Sensor

Für eine Laserlichtschranke braucht man einen Sender und einen Empfänger. Da ich keine Reflexlichtschranke baue sind beide Geräte in unterschiedlichen Gehäusen verbaut. Als Sender nutze ich einen alten Laserpointer, aus dem ich die Batterien entfernt habe, und diese durch zwei Kabel ersetzt habe.

Sensor_Laserpointer.jpg

Der Laserpointer benötigt 3V Spannung und 15mA Strom. Deshalb kann ich ihn direkt mit einem digitalen Output-Pin des Arduinos (max. 40mA) betreiben. Wer einen Laserpointer mit mehr Leistung nutzen will, der muss, um das Arduino nicht zu zerstören, ein Relais oder einen Transistor zwischenschalten. Der Kabelbinder am Gehäuse dient dazu, den Schalter permanent gedrückt zu halten. Wem das stört, der kann auch das Gehäuse öffnen und die Kontakte des Schalters gemeinsam verlöten.

Da der Laserpointer mit 3V arbeitet, das Arduino jedoch 5V ausgibt, benötigt man zusätzlich einen Spannungsteiler (im Grunde die einfache Variante des Logic Level Shifters).

Spannungsteiler_Sensor

Als Empfänger nutze ich einen Phototransistor (620-960nm). Dieser sitzt in einem anderen Gehäuse, doch das Gesamtpaket wird nur mit einem Kabel mit der Switchbox verbunden. Der Schaltplan unten zeigt das gesamte System, für welches ich die folgenden Komponenten verbaut habe:

  • 1 1kΩ Widerstand für den Spannungsteiler
  • 1 2.2kΩ Widerstand für den Spannungsteiler
  • 1 10kΩ Pull-Down Widerstand für den Phototransistor
  • 1 Phototransistor (620-960nm) (BPW 16N oder ähnlich)
  • 1 alten Laserpointer mit rotem Licht (Hier als LED dargestellt)

Water_Drop_Pro_Sensor_Shematic

Achtung, ich nutze einen Laserpointer mit rotem Licht! Falls jemand einen mit grünen Licht nutzen will, so muss er auch den Phototransistor dementsprechend anpassen, da dieser hier nur auf rotes Licht reagiert! Um die Auswahl des Phototransistors zu erleichtern, gibt es hier eine gute Tabelle.

Der Sensor Jack ist ein einfacher 5-poliger Mikrofonstecker, der an die passende Buchse an der Switchbox gesteckt werden kann. Man kann natürlich auch die beiden GND-Leitungen zusammenfassen, da sie an der Platine sowieso am selben Pin landen, doch dann würde es an der Switchbox zwei 4-polige Buchsen geben, was zu einer Verwechslung führen könnte, und so Schäden am Gerät verursachen würde.

Switchbox_Sensor_Interface

Der Stecker wird direkt mit den passende Terminal-Block-Pins an der Platine verbunden. Man muss dann nur noch darauf achten, am anderen Ende des Steckers auch die richtigen Kabel mit dem Phototransistor oder dem Laserpointer zu verbinden. Hierfür nutze ich den akustischen Diodenprüfer eines Multimeters.

Sensor_Pinout_Real.jpg

Achtung, wer hier unachtsam ist könnte einen Kurzschluss verursachen. Also lieber alles zweimal prüfen!

Zur Sicherheit sollte man das Fritzing-File im Projektverzeichnis (der Link am Beginn des Beitrags) zu Rate ziehen.

Fritzing_Sensor

Das Gehäuse des Phototransistors ist ein kleines, günstiges Kunststoffgehäuse. Ich habe drei Bohrungen angebracht:

  • Die Bohrung für das 5-polige Kabel von der Switchbox
  • Eine kleinere Bohrung für das 2-polige Versorgungskabel des Lasers
  • Eine Bohrung als Öffnung für den Laser

Der Rahmen

Um den Rahmen der Pro-Version nur gering umzubauen, habe ich mich für die einfachste Variante entschieden den Sensor zu montieren: Ein dünnes Brett wird an den mittleren Balken geschraubt. So muss man nichts weiteres tun, als zwei Bohrungen (D=12mm) in den Balken zu bohren, und darin jeweils eine Gewindemutter (M8) zu fixieren. So kann man später das Brett einfach mit zwei M8-Flügelschrauben befestigen.

Frame_006.jpg

Das Brett kann man gestallten wie man es gerne möchte, und da ich nicht davon ausgehe, dass jeder die selben Gehäuse für Sender und Empfänger der Laserlichtschranke zur Verfügung hat, habe ich das Brett auch nicht in das Zip-File hinzugefügt. Einzige „Bedingung“ ist, dass die beiden Flügelschrauben einen Abstand von 130mm zur Mitte des Balkens haben (also 260mm Abstand zueinander).

Der Code

Natürlich befindet sich die neueste Version des Programmcodes auch in dem Zip-File. Für all‘ jene, die sich das Programm gerne auf GitHub ansehen möchten: Der Sensor wird erst ab der Version 1.3.0 unterstützt, und läuft ohne Probleme seit der Version 1.3.2.

Um den Sensor auch nutzen zu können, muss, sobald der Host mit der Switchbox verbunden ist, das Häkchen neben dem Start-Button gesetzt werden.

Software_Sensor_Mask

Danach kann man links in der Software die Parameter einstellen.

  • Height 1: Dies ist die Distanz zwischen dem Fallobjekt und dem Sensor oberhalb des Sensors. Hält man beispielsweise einen Apfel 10cm über dem Sensor, bevor man ihn fallen lässt, so muss man hier 100mm einstellen.
  • Height 2: Hier wird die Distanz zwischen Sensor und Boden festgelegt.
  • Delay: Das Delay wird automatisch berechnet, jedoch kann man es noch zusätzlich variieren, je nachdem wie das Bild später aussehen soll.

Ist das Häkchen gesetzt, so werden beim Vorgang die Ventile nicht genutzt! Sollte ich Anfragen bekommen, die Ventile und den Sensor gleichzeitig nutzen zu können, dann werde ich das ins Programm bringen, ansonsten sehe ich für mich keinen Sinn beides gleichzeitig zu nutzen.

Ist der Sensor zu empfindlich, also löst die Kamera aus, ohne dass etwas durch den Sensor fällt? Kein Problem, in den Optionen (Tools -> Options) kann die Empfindlichkeit eingestellt werden. Null ist hierbei die maximale Empfindlichkeit, und 10 die niedrigste (ich weiß, konträr, aber das liegt an meiner Faulheit).

Software_Sensor_Options_Mask

Der Sensortyp gibt an, ob man einen analogen oder einen digitalen Sensor nutzt. Der Sensor, den ich hier vorgestellt habe ist ein analoger, da er den Spannungswert direkt an den Controller schickt (ohne weitere Verarbeitung). Ein digitaler Sensor bereitet das Signal vorher auf, bevor er einen digitalen Wert (5V oder 0V) an den Controller schickt.

Ein solcher Sensor kann zum Beispiel als Reflexlichttaster ausgeführt sein.

Light_Sensor_Advanced

Hierbei muss man darauf achten, dass das digitale Signal LOW wird, wenn ein Objekt erkannt wird, ansonsten funktioniert der Sensor nicht mit der Switchbox.

Das Ergebnis

Um es zu testen habe ich in der Software das Delay auf Null gesetzt, und den Blitz die Arbeit machen lassen. Soll heißen, ich habe die Kamera 4 Sekunden in einem halbdunklen Raum belichten lassen, und der Blitz hat dann das Fallobjekt „eingefangen“.

In_Flight.jpg

Das Bild ist jetzt nicht gerade das schönste, aber es zeigt recht gut wie schnell das System reagieren kann. Wer die Kamera die Arbeit machen lässt, der muss die Verzögerung des Verschlusses mit einrechnen (trotz aktivierter Spiegelverriegelung). Hat man diese Zeit erst einmal durch Probieren gefunden, kann man sie als mehr oder weniger konstant annehmen und damit arbeiten.

Ein paar letzte Worte. Man kann neben diesem Sensor auch noch andere anschließen, etwa Schallsensoren. Wer mehr dazu wissen will, der kann mich gerne per Mail oder in einem Kommentar dazu fragen. Sollten mehrere Anfragen kommen, so schreibe ich gerne einen weiteren Blogeintrag zu diesem Thema.

Edit (22.06.2016)

Wer das Fallobjekt immer aus der gleichen Höhe fallen lassen, und zwar ohne es zudem noch halten zu müssen, der findet die Idee von Resterampe Berlin sicher genau so fein wie ich! Entweder man nutzt dazu einen separaten Mikrocontroller oder legt die Versorgung (Vcc & GND) des Lasers mit der des Phototransistors zusammen (Relais oder Transistor dazwischen und eine eigene Versorgung wegen des maximalen Stroms). Bei dem zweiten Punkt muss man allerdings das Programm des Arduinos etwas ändern, sodass das Servo auch arbeitet. Wer dazu Fragen hat: Einfach ein Kommentar!

So, das war’s.

.

Arduino & Nunchuk

Eines meiner Projekte benötigt einen Joystick und 2 Buttons als Eingabemöglichkeit. Das ganze selbst zu bauen wäre nicht das Problem, doch als ich hörte, dass der Wii Nunchuk mit I2C arbeitet konnte ich gar nicht anders, als ihn für meine Zwecke zu nutzen.

Setup_Bokeh.jpg

In diesem Beitrag zeige ich, wie man die Daten vom Controller via I2C auslesen kann. Diese Daten werden dann auf einem Display und mit 6 LEDs dargestellt.

Der elektrische Aufbau

Für dieses Tutorial benötigt man die folgenden Bauteile

  • 1 Arduino UNO/Nano
  • 1 Wii Nunchuk
  • 1 0.96″ I2C Display
  • 6 LEDs
  • 6 220Ω Widerstände für die LEDs

Neben dem Steckbrett gibt’s auch noch den Schaltplan, da es wegen der LED-Anordnung recht eng geworden ist.

Wii_Nunchuk_Schaltplan

Wii_Nunchuk_Steckplatine

In Real sieht der Setup dann etwa so aus:

Setup

Achtung! Die Versorgungsspannung des Nunchuks beträgt 3.3V. Er darf auf keinen Fall an die 5V Leitung angeschlossen werden, da dies ihn sonst beschädigen würde! Er hat zudem einen Logic Level Shifter integriert, was die Nutzung der I2C-Pins ohne weitere Maßnahmen erlaubt.

Die meisten I2C-Displays dieser Größe arbeiten mit 5V. Mehr zu dem Display und dessen Verwendung kann man hier nachlesen. Wem das Lesen zu viel ist, der kann sich hier die nötige Programmbibliothek für den Display herunterladen und in das Standard-Verzeichnis für Arduino-libs (../Arduino/libraries/) kopieren.

Der Code

Wie immer: GitHub.

Im Hauptverzeichnis auf GitHub findet man 3 *.ino-Dateien. Wer die Standard-Arduino-IDE nutzt, der muss lediglich das Hauptfile – Wii_Nunchuk.ino – öffnen. Die beiden anderen Dateien werden automatisch in neue Tabs geladen. Wenn alle Dateien offen sind folgt hier die Erklärung der Funktionen:

  • setup. Pins festlegen, Controller initialisieren, controllerspezifische Kalibrierung festlegen (dazu später mehr), Display initialisieren.
  • loop. Daten vom Nunchuk anfordern, diese dann auf ein „hübsches“ Outputformat für den Display bringen und ausgeben. Zum Schluss noch die LEDs passend zum Signal ansteuern.
  • printFormNum. Damit die Zahlen rechtsgebunden am Display erscheinen wird der auszugebende String an die Länge der Zahl angepasst.
  • sendDataToNunchuk. Hier wird mit Hilfe der Wire.h-Library ein Datensatz an den Nunchuk gesendet. Für mehr Info über die I2C-Kommunikation: http://playground.arduino.cc/Learning/I2C.
  • initNunchuk. Zu Beginn muss der Nunchuk initialisert werden, wobei festgelegt wird aus welchem Register des Nunchuks wie ausgelesen wird.
  • getNunchukData. Hier passiert das Spannende. Zuerst schickt man dem Controller einen Request für die ersten 6 Register (0x00, 0x01, …). Hinter diesen Registern verbergen sich die Postionsdaten des Joysticks und der Buttons als Zahlenwert. Hat der Controller den Request erhalten, so schickt er an das Arduino die jeweiligen Daten zurück. Die map-Funktion wird hier als controllerspezifische Kalibrierung eingesetzt.

Ist das Programm auf das Arduino geladen, so kann man bei Benützung des Nunchuks die Daten, welche von ihm empfangen werden vom Display ablesen. Zusätzlich ändert sich die Helligkeit der LEDs passend zur Eingabe.

Display_Close.JPG

Was ist nun die ominöse Kalibrierung? Die Daten, welche der Controller an den Arduino schickt sind nicht weiter verarbeitet, sie sind also Rohdaten. Nun kann es passieren, dass man den Joystick ganz nach links drückt, der Zahlenwert der X-Koordinate dennoch nicht Null ist. Um das zu ändern muss man zunächst im Setup alle die Min/Max-Values zurücksetzen auf:

nunchuk.MinX = 0;
nunchuk.MaxX = 255;
nunchuk.MinY = 0;
nunchuk.MaxY = 255;

Nun lädt man das Programm erneut auf das Arduino und notiert sich die angezeigten Werte der äußersten Position des Joysticks:

  • MinX ist jener Wert, wenn der Joystick ganz nach links gedrückt wird (zB: 29).
  • MaxX: Wert, wenn Joystick ganz nach rechts gedrückt wird (zB 228).
  • MinY: Wert, wenn Joystick nach unten gedrückt wird (zB 33).
  • MaxY: Wert, wenn Joystick nach oben gedrückt wird (zB 222).

Diese Werte trägt man dann in die Kalibrier-Variablen ein.

nunchuk.MinX = 29;
nunchuk.MaxX = 228;
nunchuk.MinY = 33;
nunchuk.MaxY = 222;

Nun ist das Programm für diesen einen Controller ausgelegt.

Das war’s, bei Fragen ist ein Kommentar immer erwünscht!

.