I²C & Bit-Shifting

English version.

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.

Arduino & Nunchuk

English version.

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!

.

 

Magnetometer Compass

English version.

Wer schon einmal mit dem Gedanken gespielt hat, einen kleinen Fahrroboter zu bauen, der wird sich sicherlich Gedanken über die Positionierung gemacht haben. Neben dem GPS benötigt man eine weitere Komponente, welche sicherlich genau so wichtig ist: dem Kompass. Man braucht ihn, damit der Roboter weis, in welche Richtung er gerade schaut. Ohne diesen müsste er sich nur mit dem GPS in eine beliebige Richtung bewegen, aus der man anschließend den Kurs bestimmen müsste.

Ich möchte in diesem Beitrag zeigen, wie man das mit einem HMC5883L-Modul einfach und schnell schaffen kann. Außerdem habe ich noch eine größere Erweiterungen parat, welche ich ebenfalls präsentieren werde. Hierfür benötigt man neben dem Kompass auch einen Schrittmotor (hier bereits gezeigt).

Das Modul

Bei dem Modul HMC5883L handelt es sich um ein preiswertes, baukleines Magnetometer, welches mit dem Mikrocontroller via I²C kommunizieren kann. Hierfür benötigt man die Wire.h Programmbibliothek, welche mit der Arduino IDE mit installiert wird. Dazu mehr bei der Code-Erklärung. Das Modul habe ich auf eBay um 1€ neu gekauft, sogar mit kostenlosen Versand!

Compass

Bei meinem Modul habe ich darauf geachtet, dass es einen Spannungsregler verbaut hat, sodass ich es in einem Bereich von 3.3V bis 5V betreiben kann. Laut Hersteller lässt sich eine Genauigkeit von 1 – 2 Grad Kurs errechnen.

Doch wie funktioniert das Modul? Wie bereits erwähnt, handelt es sich hierbei um ein Magnetometer. Dieses misst die Flussdichte des magnetischen Feldes der Erde. Hierfür werden dünne Nickel-Eisen Streifen als Widerstandselement verbaut. Legt man nun Spannung an und setzt diese Streifen einem Magnetfeld aus, so ändert sich der Brückenwiderstand. Man misst das Magnetfeld also als Spannungsänderung. Diese Elemente sind so aufgebaut, dass sich 3 verschiedene Achsen ergeben (x, y, z). Diese sind auch dementsprechend auf der Platine gekennzeichnet.

Axis

Das Modul verfügt über 3 Betriebsmodi. Continous-Mode, Single-Mode und Idle-Mode. Bei ersterem sendet das Modul durchgehend seine Daten. Beim Single-Mode sendet es nur einmal den Messwert und versetzt sich dann in den Idle-Mode. Dieser ist vergleichbar eines Energiesparzustandes. Das Modul eignet sich also auch perfekt für Anwendungen abseits dauerhaft verfügbarer Spannungsquellen.

Übrigens: Das Modul wird auch in manchen Handys, Tablets oder anderen mobilen Geräten verwendet.

Der Aufbau

Viel bedarf es nicht, lediglich dem Arduino (UNO oder Nano), dem Kompass einem Steckbrett, und (optional) einem kleinen I²C Display, welches ich hier bereits vorgestellt habe. Wer direkt die Programmbibliothek für das kleine OLED Display herunterladen möchte, der kann dies hier tun.

Compass+Display

Bei der Verdrahtung muss man sich an die Regeln des I²C-BUS halten. Dieses BUS-System nutzt lediglich 2 Datenleitungen, um Daten zu senden und zu empfangen. Insgesamt benötigt man jedoch 4 Kabel, denn die Geräte müssen mit Spannung versorgt werden (Vcc & GND).

Bei diesem Projekt lässt sich auch gleich der Vorteil des BUS-Sytsems erkennen. Ich habe zwei I²C-Module einfach miteinander verbunden. So kann man mit wenig Aufwand viele Geräte betreiben, ohne dabei wichtige Pins zu belegen.

Die Anschlüsse müssen wie folgt miteinander verbunden werden:

  • SDA ⇒ Arduino Pin A4
  • SCL ⇒ Arduino Pin A5
  • Vcc ⇒ Arduino +5V
  • GND ⇒ Arduino Ground
Sollte es dennoch etwaige Unstimmigkeiten geben, findet man hier die schematische Zeichnung sowie das Steckbrett.
,
Magnetometer_Compass_Schaltplan
,
Magnetometer_Compass_Steckplatine

Das Programm

Das Programm findet man auf GitHub.

Zuerst möchte ich die Datei Magnetometer_Compass.ino vorstellen, die Erweiterung folgt zum Schluss.

Die ersten paar Zeilen dienen der Festlegung der Adressen des Kompass-Modules. Wichtig ist vor allem die Hauptadresse des HMC5883. Diese ist einmalig, sodass man das Modul auch dann noch verbinden kann, wenn auch mehrere I²C Geräte angeschlossen sind.

#define HMC5883L_Address             0x1E
#define HMC5883L_Mode_Register       0x02
#define HMC5883L_Continuous_Mode     0x00
#define HMC5883L_Data_Output_Address 0x03

In der loop() wird der Winkel stetig zur Ausgabe gebracht. Dies habe ich über 2 „Timer“ gelöst, da ich den Bildschirm schneller aktualisieren will, als ich die Daten in den seriellen Monitor schreibe. Ist das Projekt in Betrieb, und hat man den seriellen Monitor gestartet, so sollte man folgenden Datenstrom erkennen.

Serial_Monitor

Das Arduino sendet also konsequent die magnetische Flussdichte aller 3 Hauptachsen, sowie den daraus errechneten Kurs.

In der Funktion readHMC5883L() werden lediglich die Messdaten des Moduls aufgerufen. Jede Achse hat 2 Register (LSB & MSB). Aus diesem Grund muss man auch insgesamt 6 Messwerte einlesen.

if(6 <= Wire.available())
{
    compass.X_Axis  = Wire.read() << 8 | Wire.read();
    compass.Z_Axis  = Wire.read() << 8 | Wire.read();
    compass.Y_Axis  = Wire.read() << 8 | Wire.read();
}

Kommen wir nun zur wichtigsten Funktion, der getAngle(). Natürlich müssen zuerst die Daten vom Sensor ausgelesen werden, doch dann beginnt der eigentliche Spaß an der Sache. Man muss sich überlegen, wie man aus der magnetischen Flussdichte den Kurs errechnen kann.

Heading

Aus der Grafik, welche die Erde darstellen soll, lässt sich erkennen, dass sich dies mit Hilfe des Arkustangens bewerkstelligen lässt. Erhält man beispielsweise als Datenwert für x=1 und für y=1.7235 (Wurzel aus 3), so erkennt man, dass der Arkustangens den Wert 1.0472 (PI / 3) zurückgibt.

float Angle = atan2(-compass.Y_Axis, compass.X_Axis);

Das Ergebnis erhält man in Radiant. Für eine sinnvolle Kursangabe benötigt man den Wert jedoch in Grad. Die Umrechnung ist hier sehr einfach, denn

GRAD = RADIANT * (180 / PI)

Und schon hat man den Kurs im Bereich von -180° bis 180° berechnet. Um das auf das etwas leserlichere Format von 0° bis 360° zu bringen, muss man den Winkel, sofern er kleiner als Null ist, einfach mit 360 addieren.

if (Angle < 0)
    Angle += 360;

Nun könnte man das Ergebnis auch dabei belassen, wenn es da nicht auch noch die Missweisung gäbe. Das Erdmagnetfeld verläuft nicht konstant von Süd- nach Nordpol. Es ändert seinen Winkel immer wieder etwas. Des weiteren liegen magnetischer Nordpol und geografischer Nordpol nicht übereinander. Aus diesem Grund existiert für jeden Ort auf der Welt eine Deklination. Man kann diese für seine aktuelle Position auf der folgenden Webseite herausfinden: http://magnetic-declination.com/

Deklination

Quelle: Wikipedia

Die Missweisung wird in Grad und Minuten angegeben. Um sie korrekt zum Winkel addieren zu können, muss man diese in Grad umrechnen. Die Grade können auch negativ sein (Ost = positiv, West = negativ) und müssen auch dementsprechend eingesetzt werden.. Dazu gibt es die folgende Formel:

(DEGREE + (MINUTES / 60)) / (180 / PI)

Zur besseren Übersicht habe ich die Grade und die Minuten als eigene Variable deklariert.

byte  Declination_Degree = +3;
byte  Declination_Minute = 57;
float Declination = (Declination_Degree + (Declination_Minute / 60)) / (180 / M_PI);
Angle += Declination;

Das wars. So einfach kann man sich selbst einen kleinen Kompass bauen!

Erweiterung

Hier möchte ich mit einem Schrittmotor eine Kompassnadel simulieren. Dazu habe ich die selbe selbst gebastelte Gradscheibe genutzt, wie ich sie im hier beschriebenen Tutorial bereits verwendet habe. Auch die Verdrahtung des Schrittmotors mit dem Arduino ist ident:

  • IN1 ⇒ Pin 2
  • IN2 ⇒ Pin 3
  • IN3 ⇒ Pin 4
  • IN4 ⇒ Pin 5

Es ist quasi nur eine Erweiterung des Schrittmotors um einen Kompass.

Compass+Stepper

Wenn alles zusammengebaut ist, sollte es so ähnlich aussehen. Das kleine Board ganz rechts auf dem Steckbrett ist ein MPU6050 Beschleunigungssensor. Diesen werde ich demnächst einmal näher beschreiben.

Das Programm besteht nun aus zwei kombinierten Programmen. Zum einen aus der Goto_Position.ino und zum anderen aus der Magnetometer_Compass.ino. Zusammen ist aus beiden das hier beschriebene File Magnetometer_Compass_With_Stepper.ino entstanden.

Ist das Programm auf das Arduino übertragen, so wartet dieses auf die momentane Position des Schrittmotors in Grad (0° – 360°), denn das Arduino kann ja nicht wissen, wo der Zeiger gerade steht. Erst wenn die Position via seriellen Monitor übertragen wurde, beginnt das Arduino den Zeiger auf die vom Kompass ermittelte Position zu drehen.

Das war’s. Einfach eine kleine Spielerei!

OLED I²C 128×64 MONOCHROME LIBRARY

English version.

Ich habe mir vor einiger Zeit einen 0,96 Zoll Display zugelegt, welcher mit dem BUS-System I²C arbeitet. Der kleine Display hat nicht mehr als 10€ gekostet, was ihn sehr attraktiv für schnelle Hacks und kleinere Projekte macht.

Display_Real_Image

Der Aufbau

Der Display hat 4 Pins, welche auch eindeutig gekennzeichnet sein sollten, und wie folgt mit dem Arduino UNO verbunden werden.

  • SDA ⇒ A4
  • SCL ⇒ A5
  • VCC ⇒ 5V
  • GND ⇒ Ground

A4 und A5 dienen am Arduino UNO hier als BUS-Schnittstelle und können daher auch nicht mehr für andere Zwecke belegt werden. Viele Displays arbeiten auch oder nur mit 3,3V als Versorgungsspannung, dies sollte man unbedingt vorher überprüfen. Je nach Arduino und verwendeten Prozessor liegen die Kommunikationspins an unterschiedlichen Stellen. Dies kann man auf der Arduino Homepage für jedes Board nachlesen.

How it works

Der Display wird, wie bereits kurz erwähnt, mit dem Arduino via I2C verbunden. Damit das Arduino dies auch „verstehen“ kann, benötigt man die Library „Wire.h“, welche mit der IDE mit installiert wird. Ein großer Pluspunkt an dieser Verbindung ist, dass die Kommunikation für mehrere Geräte funktioniert, darauf wird hier jedoch nicht weiter eingegangen. Der Vorteil ist, dass nur 2 Pins am Arduino für den Display benötigt werden. Dies spart Platz für andere Komponenten!

Um den Display nutzen zu können, muss man ihm bestimmte Daten senden. Dies erfolgt über die Wire-Library.

Wire.beginTransmission(Device_Address);
Wire.write(Register_Address);
Wire.write(Data);
Wire.endTransmission();

Die hierbei verwendeten Parameter sind die Geräteadresse, die Registeradresse und der Datenblock. Die Geräteadresse lautet bei meinem Display 0x3C, diese kann jedoch von Display zu Display verscheiden sein. Hierfür muss man nur im Datenblatt des Gerätes nachschlagen. Die Registeradresse unterteil sich hier in zwei Teile, den Command Mode 0x80 und den Data Mode 0x40. Hier wird angegeben, ob nun ein Befehl oder ein Datenblock folgt. Der dritte Term beinhaltet den Befehl oder die Daten. Die Liste an Befehlen und sendbaren Daten findet man ebenfalls im Datenblatt, oder im Internet.

Der Display ist in 128 Spalten und 8 Zeilen mit jeweils 8 Pixeln als Block unterteilt. Diese Blöcke nennt man auch Pages. Die Pixel in einem solchen Block werden durch eine 8-stellige Binärzahl gesteuert. Übergeben muss man diese jedoch als HEX-Nummer.

Um beispielsweise den Pixel in der oberen linken Ecke zu schalten, muss man folgenden Satz an Befehlen senden:

Wire.beginTransmission(0x3C);  // Display Adresse
Wire.write(0x40);              // Data Mode - es folgen Daten
Wire.write(0x01);              // 0x01 = 00000001
Wire.endTransmission();        // Beenden der Übertragung

Um zum nächsten Block zu gelangen muss man lediglich einen neuen Satz an Befehlen senden, der Display erhöht den Zähler bei jedem Empfangen von Daten selbst. Je nachdem, welcher Modus am Ende der Zeile aktiv ist, setzt der Display folgende Aktion:

  • Horizontal Addressing: Der Zähler springt zum ersten Block in der nächsten Zeile.
  • Page Addressing: Der Zähler springt zum ersten Block in der momentanen Zeile.

Display_Shematic

Anhand der Grafik oben kann man erkennen, wie die Pages aufgebaut sind. Die langen, vertikalen Balken symbolisieren jeweils eine Page. Die grün eingefärbte Page ist jene, welche in dem Beispiel oben beschrieben wurde. Hier wurde nur der erste Pixel (der oberste Pixel) angesprochen. Nach diesem Befehl wird der Zähler erhöht, und die Page rechts neben der grünen ist an der Reihe.

Der Code

Wie immer liegt der Programmcode auf GitHub.

Um die Library nutzen zu können, muss diese zuerst in das Bibliothekenverzeichnis der IDE kopiert werden. Zusätzlich benötigt man auch noch die Wire-Library, welche sich bereits im Verzeichnis für Bibliotheken befinden sollte.

Nun steht die OLED_I2C_128x64_Monochrome zur Verfügung. Um nicht immer diesen langen Namen schreiben zu müssen, wird die Klasse als Display erstellt, und das Objekt als lcd.

Das folgende Script soll zeigen, wie man die Library benutzt.

#include "OLED_I2C_128x64_Monochrome.h"
#include "OLED_I2C_128x64_Monochrome_Font.h"
#include <Wire.h>

void setup()
{
     lcd.initialize();
     lcd.printString("----------------", 0, 0);
     lcd.printString("THIS IS A STRING", 3, 0);
     lcd.printString("----------------", 6, 0);
}

void loop()
{
    // Nothing to do here.
}

Display_String_Image

Manche Displays, so auch meiner, benötigen 7.5V für das OLED Panel. Aus diesem Grund muss der interner Hochsetzer aktiviert werden, welcher die 5V der Versorgung umwandelt. Hierfür muss man die beiden folgenden Daten senden:

sendCommand(0x8d);
sendCommand(0x14);

In der Library ist dieser Fall bereits berücksichtigt. Wer einen Display nutzt, welcher diesen Command nicht benötigt, der muss lediglich die Zeile 9 im *.cpp File auskommentieren, oder löschen.

#define USE_REGULATOR

Um das Thema Horizontal Addressing nochmal geanuer zu erklären, habe ich hier eine kleine Aufgabe parat gestellt. Die Pages sollen reihenweise ausgemalt werden, wobei sie am Zeilenende die Zeile wechseln. Hierfür muss man den Display auf den Horizontal Mode umschalten.

Nachdem der Display ausgemalt wurde, also die letzte Page in der letzten Zeile angesprochen wurde, kehrt der Zähler wieder auf Page 0 und Zeile 0 zurück.

Danach wird der Display wieder schwarz ausgemalt – selbes Prinzip, es wird nur ein anderer Datenwert gesendet.

#include "OLED_I2C_128x64_Monochrome.h"
#include "OLED_I2C_128x64_Monochrome_Font.h"
#include <Wire.h>

void setup()
{
    lcd.initialize();
    lcd.setHorizontalMode();
}

void loop()
{
    for (byte Y = 0; Y < 8; Y++)
    {
        for (byte X = 0; X < 128; X++) 
        {
            Wire.beginTransmission(0x3C); // Display Adresse
            Wire.write(0x40);             // Data Mode
            Wire.write(0xff);             // 0x99 = 11111111
            Wire.endTransmission();       // Beenden der Übertragung
            delay(10);
        }
    }
    for (byte Y = 0; Y < 8; Y++)
    {
        for (byte X = 0; X < 128; X++) 
        {
            Wire.beginTransmission(0x3C); 
            Wire.write(0x40);             
            Wire.write(0x00);             // 0x00 = 00000000
            Wire.endTransmission();
            delay(10);
        }
    }
}

Display_Gif_small

Ich hoffe damit habe ich alle Fragen ausgeräumt, wer dennoch welche hat -> Comment Section!