I/O Expander am Pi

Der Chip ist groß und lang und hat viele Pins, damit du weist wo Oben ist,
gibt es auf der einen Seite eine halbrunde Kerbe. Achte darauf dass diese
nach Oben zeigt. Um den Chip am Pi zu verwenden brauchen wir nur 2
GPIO Pins für die SPI-Daten, dazu 3,3V und GND. Schließe den Chip so an
wie auf dem Bild zu sehen ist und orientiere dich an der Tabelle falls etwas
unklar ist.

Damit wir unsere Uhr nicht nur die Minuten anzeigen kann, nutzen wir noch die LEDs für die Stundenanzeige. Da der Raspberry aber nicht genügend Pins hat nutzen wir einen Chip, den wir über I2C ansetuern können und für uns dann die LEDs zum Leuchten bringt.

Raspberry PiI/O Expander
3.3VPin 9, Pin 18
GNDPin 10, Pin 15, Pin 16, Pin 17
SDAPin 13
SCLPin 12

I2C Aktivieren

Damit wir das machen können müssen wir zunächst den I2C bus am Pi aktivieren. Das kannst du in der raspi-config machen.

Nach dem anschließen des Chips an unsere Pi testen wir ob dieser erkannt wurde. Dafür nutzen wir den Befehl:

i2cdetect -y 1

Die Ausgabe sollte so aussehen:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                        

Die 20 zeigt uns an das der Chip erkannt wurde und welche ID das i2c Device
des Chips hat. Das brauchen wir gleich um Ihn anzusprechen.
Jetzt legen wir los und schauen uns die Pins des Chips an. Auf der linken
Seite haben wir die Pins GPB und auf der rechten Seite sind die Pins GPA .
Von beiden gibt es 8 Stück, insgesamt 16 Pins.

Bevor wir diese jetzt schalten können, müssen wir dem Chip sagen wie er die
Pins behandeln soll, als Eingang oder als Ausgang.

i2cset -y 1 0x20 0x00 0x00
i2cset -y 1 0x20 0x01 0x00

Mit diesen beiden Befehlen legen wir fest dass alle Pins Ausgänge sind.
Bitte beachte dass du diese beiden Befehle nach jedem Neustart
eingeben musst um den Chip zu aktivieren!
Wie der Befehl aufgebaut ist
erklären wir gleich, denn den selben Befehl nutzen wir auch die Pins zu
schalten.

Mit i2cset steuern wir den Chip der Befehl ist wie folgt aufgebaut.

commandI2CBUSCHIP-ADDRESSDATA-ADDRESSVALUE
i2cset -y10x200x150x01

Die 1 steht für das i2c Device am Pi. Die Alten Modelle bis B+ verwenden
anstatt der 1 bitte die 0. Die CHIP-ADDRESS ist die Adresse des Chips, die
wir oben mit dem Befehl i2cdetect -y 1 ermittelt haben. DATA-
ADDRESS ist der Bereich den wir schalten wollen und VALUE ist der
Zustand aller 8 Pins den wir als Hex-Wert übergeben.

Unsere 16 Pins unterteilen sich in GPB und GPA GPA hat die DATA-
ADDRESS 0x14 GPB hat die DATA-ADDRESS 0x15

Unsere LED haben wir an GPB0 angeschlossen. Um diese nun zum
leuchten zu bringen wäre der Befehl also folgender:

i2cset -y 1 0x20 0x15 0x01

Eine LED an GPA Pins können wir mit dem Befehl i2cset -y 1 0x20 0x14 0x01 aktivieren. Jetzt weißt du wie man die GPA und GPB Pins
ansteuert. Wir senden über unser i2c Device, 1 an den Chip 0x20 für die
GPB Pins 0x15 die Werte 0x01. Das 0x01 ist der Hexadezimal Wert von
00000001 in Binär. Pin 1 ist an, und der Rest der 8 Pins ist aus. Die Darstellung in 1 und 0 heißt Binär und begegnet uns ständig in der IT Welt. Da diese Werte aber sehr lang sind und teilweise umständlich zu lesen werden Sie gerne in Hexadezimal dargestellt. Mehr zu Hexadezimal kannst du hier erfahren: https://de.wikipedia.org/wiki/Hexadezimalsystem

Um Binär in Hexadezimal umzurechnen, kannst du dieses einfache Skript nutzen:

bin = input("Bitte Binärzahl eingeben: ")
hex = hex(int(bin, 2))
print(hex)

Wir wollen LEDs leuchten lassen!

Verbinde mehrere LEDs mit den GPB Pins und GND und nutze den
folgenden Befehl um die LEDs leuchten zu lassen.

i2cset -y 1 0x20 0x15 0x01

Das 0x01 kannst du frei ersetzen durch die Werte aus dem Skript. 0xff
schaltet zum Beispiel alle Pins an. Das wäre der Dezimalwert 11111111.

i2cset -y 1 0x20 0x15 0xff

LEDs einsetzen

In dem Kit findest sind alle LEDs enthalten die du brauchst und Ersatz falls
mal eine ausfällt oder kaputt geht. Auf dem Aufkleber ist Platz für die LEDs
durch schwarze Punkte markiert. Drücke die LEDs durch die Pappe, achte
darauf dass das kurze bein zu Mitte zeigt. Drücke die Beine der LED dann
nach Innen und Außen, aber so dass sich die Beine nicht berühren. Wir
empfehlen dir die Jumperkabel an die LEDs zu löten! Du kannst Sie
aber auch stecken.

Damit es übersichtlich bleibt, haben wir dir ein zweites Breadboard
eingepackt. Verbinde die GPA und GPB Pins des I/O Expander Chips mit
dem zweiten Breadboard.

GPB-1 wird oben in das Zweite Breadboard in Reihe eins gesteckt. GPB-2 in
2 usw. Diese Verbindungen steuern unsere LEDs. Zum Testen kannst du
auch einfach LEDs in das Breadboard stecken. Oder du Verbaust direkt die LEDs in der Uhr, das geht natürlich auch.

Stundenanzeige programmieren

Damit die LEDs auch die Uhr zeit anzeigen können erstellen wir uns dazu ein kleinens Testscript, dass dir wpäter mit einbinden können. Denn die Uhr soll später ja Minuten und Stunden gleichzeitig anzeigen.

Zunächst benötigen wir die smbus library. Mit dieser, können wir auf den SPI Bus zugreifen. Diese installieren wir so:

sudo pip install smbus

Wenn das getan ist können wir unser Skript schreiben:

import smbus
import time
from datetime import datetime as dt

bus = smbus.SMBus(1)
bus.write_byte_data(0x20, 0x00, 0x00)
bus.write_byte_data(0x20, 0x01, 0x00)

now = dt.now()
hour = now.hour % 12
print(hour)

leds = 1 << hour

bus.write_byte_data(0x20, 0x14, 0xff & leds)
bus.write_byte_data(0x20, 0x15, 0xff & (leds >> 8))

Was passiert hier?
Im ersten Block importieren wir wie gewohnt alle benötigten Bibliotheken.

Im zweiten Block sagen wir dem Chip, dass die Pins als Ausgänge verwendet werden sollen. Das ist genau das Gleiche, was wir vorhin in der Konsole gemacht haben.

Wenn dass passiert ist, holen wir uns im nächsten Block die Uhrzeit mit der Methode dt.now(). Die Uhrzeit die wir mit dieser Methode zurück bekommen ist allerdings im 24-Stunden-Format. Da unsere Uhr allerdings nur 12 Leds hat, benutzen wir wieder den Modulo- Operator. Das heißt bei allen Zahlen, die größer als 11 sind, fängt der Python wieder bei Null an zu zählen. Also z.B.: 12 Uhr wird zu 0 Uhr, 13 Uhr wird zu 1 Uhr und so weiter. Zur Kontrolle schreiben wir die aktuelle Stunde nochmal in die Ausgabe.

In Zeile 13 passiert jetzt etwas Magie. Und zwar, wollen wir die Stunden ja nicht in Binär darstellen, was zwar bestimmt auch ganz cool ist, aber sich nicht ganz so einfach ablesen lässt. Damit wir nicht jedesmal Kopfschmerzen bekommen, wenn wir die Uhrzeit ablesen, soll einfach die entsprechende LED leuchten. Damit das passiert, nutzen wir den Bitshifting-Operator. Mit diesem Operator können wir eine Binärzahl um eine bestimmte Anzahl (n) nach links oder rechts verschieben. Das Ergebnis dabei ist genau das gleiche, als würdest du die Binärzahl mit Zwei hoch n multiplizieren.
Was also in der Zeile passiert ist dass wir die 1 um n Stellen nach links verschieben. Um 4 Uhr würde das Ergeniss also zum Beispiel so aussehen:

1 << 4 = 0b10000.

(0b ist dabei einfach nur der Prefix, der anzeigt, dass es sich um eine Binärzahl handelt.) Das heißt am 5. Pin vom Chip würde jetzt eine Spannung anliegen. Wenn du die LEDs richtig angeordnet hast und die erste LED auf 12 Uhr Liegt, würde deine Uhr also 4 Uhr anzeigen.
Wenn du möchtest, dass später nicht nur die aktuelle Stunde leuchtet, sondern alle LEDs von 12 bis zur aktuellen Stunde, kannst du diese Zeile auch einfach durch diese ersetzen: leds = (1 << (hour + 1)) - 1.

Kommen wir zum letzen Block. Hier übergeben wir den berecheneten Datensatz an den Chip. Das passiert wieder so ähnich, wie vorhin in der Konsole.
Da der Chip allerdings zwei Busse hat, die wir einzeln ansprechen, müssen wir den Datensatz vorher noch aufteilen, denn ein Bus an unserem Chip hat nur 8 Ausgänge (genau ein Byte) und wir wollen ja schließlich auch noch nach 7 Uhr die Uhrzeit ablesen können. Da jeder Bus genau einen Byte braucht unser Datensatz aber länger ist, schreiben wir zunächst ein Byte (= 9 Bit) in Hexadezimal 0xff (das ist das gleiche wie 0b11111111 in Binär nur kürzer) und nuten den Und-Operator. Dieser vergleicht dann das was links und was rechts von ihm steht miteinander. Dabei geht er Bit für Bit von hinten nach vorne vor. Das heißt überall dort, wo auf beiden Seiten eine Eins steht gibt er auch eine Eins zurück. Zum Beispiel: 00001111 & 10011011 = 00001011 oder kürzer: 1111 & 10011011 = 1011 (Wie du siehst lassen sich nach vorne beliebig viele Nullen anhängen, ohne das Ergeniss zu verändern.) Da 0xff nur 8 Bit lang ist, erhalten wir also die letzten 8 Bit von dem Datensatz.
Fast geschafft, jetzt müssen wir nur noch die restlichen Bits vom Datensatz auf den anderen Bus schreiben. Dazu nutzen wir wieder den Und-Operator, da auch der Bus genau 8 Bits benötig. Allerdings wollen wir ja diesmal nicht das gleiche Byte übertragen. Desshalb nutzen wir wieder den Bitshiftig-Operator von vorhin und verschieben die Daten bevor wir den Und-Operator nutzen einfach 8 Bits nach rechts. Und schon erhalten wir das zweite Byte von unserem Datensatz und können das auf den zweiten Bus schreiben.
Uuuund geschafft

Jetzt kannst du dein Skript ausführen mit:

python3 leds.py