|
25. Lektion: I2C: Unterschied zwischen den Versionen
Aus Attraktor Wiki
|
|
(96 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) | Zeile 1: |
Zeile 1: |
| === Das Protokoll=== | | === Das Protokoll=== |
| + | Das I2C Manual kann von [https://web.archive.org/web/20221006073143if_/http://www.nxp.com/docs/en/user-guide/UM10204.pdf hier] heruntergeladen werden. |
| + | ==== Der Bus==== |
| + | [[file:I2C-Bus.png|400px|I2C Bus]] |
| + | <br> |
| + | Der I2C Bus besteht aus 2 Leitungen, SCL uns SDA. SCL ist die Taktleitung (clock) und SDA die Datenleitung. Die I2C Geräte werden paralell an den Bus angeschlossen. Beide Busleitungen müssen mit einem Widerstand an Vcc gelegt werden. |
| + | Der maximale Ausgangsstrom eines I2C IC's beträgt mindestens 3 mA. Der maximale Eingangsstrom 10 µA. Die Widerstände Rp müssen so gewählt werden, das der maximale Strom von 3 mA nicht überschritten wird. |
| + | <br> |
| + | Der I2C Bus ist ein Master/Slave System. Der Master bestimmt, was auf dem Bus geschieht. Er gibt den Takt vor und adressiert die Clients. Ein Client kann nur aktiv werden, wenn der Master ihn dazu auffordert. Deshalb haben I2C IC's häufig Interrupt Ausgänge mit denen dem Master mitgeteilt werden kann, dass Daten zum Abholen bereit stehen. |
| | | |
| + | ==== Die Datenübertragung==== |
| + | Die Übertragung erfolgt in Dateneinheiten. Eine Dateneinheit besteht aus 8 Datenbits und einem ACK-Bit. Dabei wird zuerst das MSB (most significant bit) also Bit 7 übertragen. |
| + | <br> |
| + | [[file:I2C-Telegramm.pdf.png|I2C Telegramm]] |
| + | <br> |
| + | Die erste Dateneinheit enthält die Zieladresse (7 Bit) und das R/W-Flag. Die weiteren Dateneinheiten enthalten die zu übertragenen Daten. Mit dem ACK-Flag kann der Client die weitere Übertragung verzögern, um genügend Zeit für die Verarbeitung der Daten zu haben. |
| + | <br> |
| + | [[file:I2C-Übertragung.svg.png|I2C Übertragung]] |
| + | <br> |
| + | [[file:I2C-Taktraten.png|I2C Taktraten]] |
| | | |
| === I2C Objekt erzeugen=== | | === I2C Objekt erzeugen=== |
| + | Wie alles in Micropython ist auch I2C als Klasse implementiert. Deshalb muß für jeden I2C Bus eine Instanz erzeugt werden. |
| + | <br> |
| + | Micropython kennt 2 Arten von I2C Treibern. Einmal den Hardware I2C Treiber und die Software Implementierung. |
| + | <br> |
| + | ==== Hardware I2C==== |
| + | Der Raspberry Pi Pico W enthält 2 Hardware I2C Einheiten (0 und 1). |
| + | Die default Einstellungen findet man folgendermassen: |
| + | <pre> |
| + | from machine import I2C |
| + | print(I2C(0)) |
| + | print(I2C(1)) |
| | | |
− | === I2C Objekt benutzen=== | + | I2C(0, freq=399361, scl=5, sda=4, timeout=50000) # timeout in µs |
| + | I2C(1, freq=399361, scl=7, sda=6, timeout=50000) |
| + | </pre> |
| + | Nun soll eine I2C Instanz erzeugt werden. Dabei können die Defaultwerte oder andere zulässige Werte benutzt werden. |
| + | <pre> |
| + | from machine import Pin, I2C |
| | | |
| + | i2c = I2C(0) # default assignment |
| + | i2c = I2C(1, scl=Pin(3), sda=Pin(2), freq=400_000) |
| + | </pre> |
| + | Mit dem so erzeugten I2C-Objekt können wir außer einem Busscan noch nicht viel anfangen. |
| + | <pre> |
| + | i2c.scan() # scan for peripherals, returning a list of 7-bit addresses |
| + | |
| + | i2c.writeto(42, b'123') # write 3 bytes to peripheral with 7-bit address 42 |
| + | i2c.readfrom(42, 4) # read 4 bytes from peripheral with 7-bit address 42 |
| + | |
| + | i2c.readfrom_mem(42, 8, 3) # read 3 bytes from memory of peripheral 42, |
| + | # starting at memory-address 8 in the peripheral |
| + | i2c.writeto_mem(42, 2, b'\x10') # write 1 byte to memory of peripheral 42 |
| + | # starting at address 2 in the peripheral |
| + | </pre> |
| + | Die übrigen Methoden werden in Treibermodulen für I2C Geräte benötigt. Ein solches Modul ist z.B. ssd1306.py, das wir uns gleich noch näher ansehen werden. |
| + | ===== Scan Test===== |
| + | <pre> |
| + | >>> i2c.scan() |
| + | [60] |
| + | >>> hex(i2c.scan()) |
| + | Traceback (most recent call last): |
| + | File "<stdin>", line 1, in <module> |
| + | TypeError: can't convert list to int |
| + | |
| + | >>> for adr in i2c.scan(): |
| + | print(hex(adr)) |
| + | |
| + | 0x3c |
| + | >>> |
| + | </pre> |
| + | |
| + | ==== Software I2C==== |
| + | Ein SoftI2C Objekt wird genauso erzeugt wie ein Hardware I2C Objekt, nur das die Pins und die Frequenz unbedingt angegeben werden müssen. Hier ist man frei in der Wahl der Pins. |
| + | <pre> |
| + | from machine import Pin, SoftI2C |
| + | |
| + | i2c = SoftI2C(scl=Pin(5), sda=Pin(4), freq=100_000[, timeout=50000]) |
| + | </pre> |
| + | Ansonsten gilt das bei Hardware I2C dargestellte. |
| + | <pre> |
| + | i2c.scan() # scan for devices |
| + | |
| + | i2c.readfrom(0x3a, 4) # read 4 bytes from device with address 0x3a |
| + | i2c.writeto(0x3a, '12') # write '12' to device with address 0x3a |
| + | |
| + | buf = bytearray(10) # create a buffer with 10 bytes |
| + | i2c.writeto(0x3a, buf) # write the given buffer to the peripheral |
| + | </pre> |
| | | |
| === SSD1306 Display mit I2C benutzen=== | | === SSD1306 Display mit I2C benutzen=== |
| + | Auf unserem Demoboard befindet sich ein Display mit einem SSD1306 IC das über den I2C angesteuert wird. Auf dem Board ist SCL = GPIO21 SDA = GPIO20. Das entspricht nicht der default Belegung, aber ist eine mögliche Belegung für I2C0. |
| + | |
| + | ==== Eine Display Instanz erstellen==== |
| + | Wenn wir ein I2C Display nutzen wollen, so müssen wir zuerst eine I2C Instanz erstellen. Da wir nicht die default Pin nutzen, müssen wir die Pins angeben. |
| <br> | | <br> |
− | * Display anschließen
| + | Anschließend können wir eine SSD1306_I2C Instanz erzeugen. Damit haben wir jetzt Zugriff auf das Display. |
− | ** https://docs.micropython.org/en/latest/esp8266/tutorial/ssd1306.html
| + | |
− | ** https://github.com/TimAEllis/ssd1306-py/blob/master/ssd1306.py
| + | |
− | ** https://www.grzesina.de/az/blog_robotcar/ssd1306.py
| + | |
| | | |
| + | <pre> |
| + | from machine import Pin, I2C |
| + | from ssd1306 import SSD1306_I2C |
| + | |
| + | i2c = I2C(0, sda=Pin(20), scl=Pin(21)) |
| + | oled = SSD1306_I2C(128, 64, i2c) |
| + | </pre> |
| + | |
| + | ==== Texte ausgeben==== |
| + | Die Anzeige von Texten ist sehr einfach. Dafür gibt es die Methode '''.text(String, x, y, Farbe)'''. <br> |
| + | Wobei die Farbe hier '''0 = Schwarz''' und '''1 = Weiss''' sein kann. <br> |
| + | Die x,y Koordinaten beziehen sich auf Pixel nicht auf Zeichen.<br> |
| + | Der Ursprung liegt oben links.<br> |
| + | Zuerst sollte der Bildschirm gelöscht werden: '''oled.fill(0)'''<br> |
| + | Dann wird der Text in den Framebuffer geschrieben '''oled.text('Hallo', 10, 10, 1)'''<br> |
| + | Schließlich wird der Framebuffer zum Display kopiert und so der Text angezeigt. '''oled.show()'''<br> |
| + | |
| + | <pre> |
| + | oled.fill(0) |
| + | oled.text('Hallo', 10, 10, 1) |
| + | oled.show() |
| + | </pre> |
| + | |
| + | ==== Grafische Ausgaben==== |
| + | |
| + | Vorab noch eine Übersicht der Display Befehle: |
| + | <pre> |
| + | oled.poweroff() # Display ausschalten. Der Inhalt bleibt erhalten |
| + | oled.poweron() # Display einschalten. Der Inhalt wird wieder angezeigt |
| + | oled.contrast(0) # dunkel |
| + | oled.contrast(255) # hell |
| + | oled.invert(1) # Display wird invertiert angezeigt |
| + | oled.invert(0) # Display normal |
| + | |
| + | oled.show() # write the contents of the FrameBuffer to display memory |
| + | </pre> |
| + | Hier die von '''framebuf''' geerbten Funktionen: |
| + | <pre> |
| + | oled.fill(0) # Der ganze Schirm wird mit der Farbe=0 gefüllt. |
| + | oled.pixel(0, 10) # Hole den Pixelwert von x=0, y=10 |
| + | oled.pixel(0, 10, 1) # Setze den Pixelwert vonx=0, y=10 auf Farbe=1 |
| + | oled.hline(0, 8, 4, 1) # Zeichne eine horizontale Line ab x=0, y=8, Länge=4, Farbe=1 |
| + | oled.vline(0, 8, 4, 1) # Zeichne eine vertikale Line ab x=0, y=8, Länge=4, Farbe=1 |
| + | oled.line(0, 0, 127, 63, 1) # Zeichne eine Linie von 0,0 to 127,63 - 6.Parameter: f=gefüllt ist True |
| + | oled.rect(10, 10, 107, 43, 1) # Zeichne eine rechteckige Umrandung von 10,10 nach 117,53, Farbe=1 |
| + | oled.fill_rect(10, 10, 107, 43, 1) # Zeichne ein gefülltes Rechteck von 10,10 nach 117,53, Farbe=1 |
| + | oled.text('Hello World', 0, 0, 1) # Zeichne Text ab x=0, y=0, Farbe=1 |
| + | oled.scroll(20, 0) # Scrolle 20 Pixel nach rechts |
| + | oled.ellipse(x, y, xr, yr, c[, f, m])# Zeichne eine Ellipse, x,y=Mittelpunkt, xr, yr=die beiden Radien, c=Farbe |
| + | # [f=gefüllt ist True, m=Quadrant( Bit 0 Q1, b1 Q2, b2 Q3 und b3 Q4)] |
| + | oled.poly(x, y, coords, c[, f]) # Zeichne ein geschlossenes Polygon. x,y=Startpunkt, coords=Array mit Punkten relativ, c= Farbe |
| + | # [, f=gefüllt ist True] |
| + | # import array |
| + | # coords = array.array('h', [10,10, 20,-20, 30,30]) |
| + | </pre> |
| + | Als Beispiel für eine grafische Ausgabe habe ich das Micropython-Symbol heraus gegriffen: |
| + | <pre> |
| + | from machine import Pin, I2C |
| + | from ssd1306 import SSD1306_I2C |
| + | |
| + | i2c = I2C(0, sda=Pin(20), scl=Pin(21)) |
| + | oled = SSD1306_I2C(128, 64, i2c) |
| + | |
| + | oled.fill(0) |
| + | oled.fill_rect(0, 0, 32, 32, 1) |
| + | oled.fill_rect(2, 2, 28, 28, 0) |
| + | oled.vline(9, 8, 22, 1) |
| + | oled.vline(16, 2, 22, 1) |
| + | oled.vline(23, 8, 22, 1) |
| + | oled.fill_rect(26, 24, 2, 4, 1) |
| + | oled.show() |
| + | </pre> |
| + | |
| + | ==== Die Praxis==== |
| + | Hier eine kleine Demo (): |
| + | <pre> |
| + | from machine import Pin, I2C |
| + | from ssd1306 import SSD1306_I2C |
| + | |
| + | i2c = I2C(0, sda=Pin(20), scl=Pin(21)) |
| + | oled = SSD1306_I2C(128, 64, i2c) |
| + | |
| + | oled.fill(0) |
| + | oled.text("Attraktor e.V.", 10, 0) |
| + | oled.text("Hello World!", 16, 32) |
| + | oled.show() |
| + | </pre> |
| + | Hier ein etwas umfangreicheres Beispiel mit grafischen Elementen: |
| + | <pre> |
| + | from machine import Pin, I2C |
| + | from ssd1306 import SSD1306_I2C |
| + | |
| + | i2c = I2C(0, sda=Pin(20), scl=Pin(21)) |
| + | oled = SSD1306_I2C(128, 64, i2c) |
| + | |
| + | oled.fill(0) |
| + | oled.fill_rect(0, 0, 32, 32, 1) |
| + | oled.fill_rect(2, 2, 28, 28, 0) |
| + | oled.vline(9, 8, 22, 1) |
| + | oled.vline(16, 2, 22, 1) |
| + | oled.vline(23, 8, 22, 1) |
| + | oled.fill_rect(26, 24, 2, 4, 1) |
| + | oled.text('MicroPython', 40, 0, 1) |
| + | oled.text('SSD1306', 40, 12, 1) |
| + | oled.text('OLED 128x64', 40, 24, 1) |
| + | oled.show() |
| + | |
| + | </pre> |
| + | |
| + | === Das SSD1306 IC=== |
| + | |
| + | [[media:ssd1306-datasheet.pdf|SSD1306 Datenblatt]] |
| + | |
| + | === SSD1306.py intern=== |
| + | |
| + | Nun wollen wir einen Blick in das Modul ssd1306.py werfen: |
| + | <pre> |
| + | # MicroPython SSD1306 OLED driver, I2C and SPI interfaces |
| + | |
| + | from micropython import const |
| + | import framebuf |
| + | |
| + | |
| + | # register definitions |
| + | SET_CONTRAST = const(0x81) |
| + | SET_ENTIRE_ON = const(0xA4) |
| + | ... |
| + | |
| + | class SSD1306(framebuf.FrameBuffer): |
| + | def __init__(self, width, height, external_vcc): |
| + | ... |
| + | self.init_display() |
| + | |
| + | def init_display(self): |
| + | for cmd in (SET_DISP | 0x00, ... SET_DISP | 0x01) |
| + | self.write_cmd(cmd) |
| + | self.fill(0) |
| + | self.show() |
| + | |
| + | def poweroff(self): |
| + | self.write_cmd(SET_DISP | 0x00) |
| + | |
| + | def poweron(self): |
| + | self.write_cmd(SET_DISP | 0x01) |
| + | |
| + | def contrast(self, contrast): |
| + | self.write_cmd(SET_CONTRAST) |
| + | self.write_cmd(contrast) |
| + | |
| + | def invert(self, invert): |
| + | self.write_cmd(SET_NORM_INV | (invert & 1)) |
| + | |
| + | def show(self): |
| + | x0 = 0 |
| + | ... |
| + | self.write_data(self.buffer) |
| + | </pre> |
| + | Die Klasse SSD1306 erbt von der Klasse framebuf. Alle Methoden aus framebuf stehen deshalb auch in SSD1306 zur Verfügung. Dazu später. |
| + | <br> |
| + | In der Klasse SSD1306 wird alles das definiert, was zur Steuerung des Displays benötigt wird. Das sind die Initialisierung des Displays, Das Ein- und Ausschalten, die Kontrasteinstellung, die inverse Darstellung und die Übertragung des Framebuffers zum Display. Das geschieht unabhängig von der verwendeten Schnittstelle. Die Methoden '''write_cmd()''' und '''write_data()''' stellen die Brücke zur Schnittstelle her. Diese sind in den Klassen SSD1306_I2C uns SSD1306_SPI definiert. Diese beiden Klassen erben die Klasse SSD1306 und somit die Steuerung des Displays. |
| + | |
| + | <pre> |
| + | class SSD1306_I2C(SSD1306): |
| + | def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): |
| + | ... |
| + | |
| + | def write_cmd(self, cmd): |
| + | self.temp[0] = 0x80 # Co=1, D/C#=0 |
| + | self.temp[1] = cmd |
| + | self.i2c.writeto(self.addr, self.temp) |
| + | |
| + | def write_data(self, buf): |
| + | self.write_list[1] = buf |
| + | self.i2c.writeto(self.addr, self.write_list) |
| + | </pre> |
| + | |
| + | ==== Das Modul framebuf==== |
| + | Das Modul framebuf ist im Lieferumfang von Micropython enthalten. Es ermöglicht es im Speicher ein Abbild der Displayausgabe vorzubereiten. |
| + | |
| + | <pre> |
| + | >>> import framebuf |
| + | |
| + | >>> dir(framebuf) |
| + | ['__class__', '__name__', '__dict__', 'FrameBuffer', 'FrameBuffer1', 'GS2_HMSB', 'GS4_HMSB', 'GS8', |
| + | 'MONO_HLSB', 'MONO_HMSB', 'MONO_VLSB', 'MVLSB', 'RGB565'] |
| + | |
| + | >>> dir(framebuf.FrameBuffer) |
| + | ['__class__', '__name__', '__bases__', '__dict__', 'blit', 'ellipse', 'fill', 'fill_rect', 'hline', 'line', 'pixel', 'poly', |
| + | 'rect', 'scroll', 'text', 'vline'] |
| + | >>> |
| + | </pre> |
| + | |
| + | Die Display Instanz erbt alle Methoden aus FrameBuffer, SSD1306 und SSD1306_I2C. |
| + | |
| + | === Bus Erweiterung=== |
| + | Die I2C-IC's werden über geräteinterne Adressen angesprochen. Meist lassen sich die IC's auf mehrere Adressen einstellen. Das reicht aber nicht immer. Bei unserem Display sind maximal 2 Adressen möglich. Diese Einstellmöglichkeit ist aber auf den Break-Out-Boards häufig nicht vorgesehen. Um bei der Adressierung flexibler sein zu können, gibt es ein IC, dass einen I2C-Bus auf maximal 8 Busse umsetzt. Dabei kann nur einer, oder auch mehrere der Ausgangsbusse aktiv sein. |
| * TCA9548a | | * TCA9548a |
− | ** https://github.com/mcauser/microphyton-tca9548a | + | [[media:tca9548a.pdf|Datenblatt TCA9548a]] |
| + | ** https://github.com/mcauser/micropython-tca9548a |
| + | |
| + | ==Navigation== |
| + | [[Micropython_Kurs_2023_-_Teil_2|Zurück zur "Micropython Kurs 2023 Teil 2" Startseite]]<br> |
| + | [[Micropython Kurs 2023|Zurück zur "Micropython Kurs 2023" Startseite]]<br> |
| + | [[Programmieren|Zurück zur Programmieren Startseite]]<br> |
| + | [[Attraktor_Wiki|Zurück zur Wiki Startseite]]<br> |
Aktuelle Version vom 23. Januar 2024, 19:14 Uhr
Das Protokoll
Das I2C Manual kann von hier heruntergeladen werden.
Der Bus
Der I2C Bus besteht aus 2 Leitungen, SCL uns SDA. SCL ist die Taktleitung (clock) und SDA die Datenleitung. Die I2C Geräte werden paralell an den Bus angeschlossen. Beide Busleitungen müssen mit einem Widerstand an Vcc gelegt werden.
Der maximale Ausgangsstrom eines I2C IC's beträgt mindestens 3 mA. Der maximale Eingangsstrom 10 µA. Die Widerstände Rp müssen so gewählt werden, das der maximale Strom von 3 mA nicht überschritten wird.
Der I2C Bus ist ein Master/Slave System. Der Master bestimmt, was auf dem Bus geschieht. Er gibt den Takt vor und adressiert die Clients. Ein Client kann nur aktiv werden, wenn der Master ihn dazu auffordert. Deshalb haben I2C IC's häufig Interrupt Ausgänge mit denen dem Master mitgeteilt werden kann, dass Daten zum Abholen bereit stehen.
Die Datenübertragung
Die Übertragung erfolgt in Dateneinheiten. Eine Dateneinheit besteht aus 8 Datenbits und einem ACK-Bit. Dabei wird zuerst das MSB (most significant bit) also Bit 7 übertragen.
Die erste Dateneinheit enthält die Zieladresse (7 Bit) und das R/W-Flag. Die weiteren Dateneinheiten enthalten die zu übertragenen Daten. Mit dem ACK-Flag kann der Client die weitere Übertragung verzögern, um genügend Zeit für die Verarbeitung der Daten zu haben.
I2C Objekt erzeugen
Wie alles in Micropython ist auch I2C als Klasse implementiert. Deshalb muß für jeden I2C Bus eine Instanz erzeugt werden.
Micropython kennt 2 Arten von I2C Treibern. Einmal den Hardware I2C Treiber und die Software Implementierung.
Hardware I2C
Der Raspberry Pi Pico W enthält 2 Hardware I2C Einheiten (0 und 1).
Die default Einstellungen findet man folgendermassen:
from machine import I2C
print(I2C(0))
print(I2C(1))
I2C(0, freq=399361, scl=5, sda=4, timeout=50000) # timeout in µs
I2C(1, freq=399361, scl=7, sda=6, timeout=50000)
Nun soll eine I2C Instanz erzeugt werden. Dabei können die Defaultwerte oder andere zulässige Werte benutzt werden.
from machine import Pin, I2C
i2c = I2C(0) # default assignment
i2c = I2C(1, scl=Pin(3), sda=Pin(2), freq=400_000)
Mit dem so erzeugten I2C-Objekt können wir außer einem Busscan noch nicht viel anfangen.
i2c.scan() # scan for peripherals, returning a list of 7-bit addresses
i2c.writeto(42, b'123') # write 3 bytes to peripheral with 7-bit address 42
i2c.readfrom(42, 4) # read 4 bytes from peripheral with 7-bit address 42
i2c.readfrom_mem(42, 8, 3) # read 3 bytes from memory of peripheral 42,
# starting at memory-address 8 in the peripheral
i2c.writeto_mem(42, 2, b'\x10') # write 1 byte to memory of peripheral 42
# starting at address 2 in the peripheral
Die übrigen Methoden werden in Treibermodulen für I2C Geräte benötigt. Ein solches Modul ist z.B. ssd1306.py, das wir uns gleich noch näher ansehen werden.
Scan Test
>>> i2c.scan()
[60]
>>> hex(i2c.scan())
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't convert list to int
>>> for adr in i2c.scan():
print(hex(adr))
0x3c
>>>
Software I2C
Ein SoftI2C Objekt wird genauso erzeugt wie ein Hardware I2C Objekt, nur das die Pins und die Frequenz unbedingt angegeben werden müssen. Hier ist man frei in der Wahl der Pins.
from machine import Pin, SoftI2C
i2c = SoftI2C(scl=Pin(5), sda=Pin(4), freq=100_000[, timeout=50000])
Ansonsten gilt das bei Hardware I2C dargestellte.
i2c.scan() # scan for devices
i2c.readfrom(0x3a, 4) # read 4 bytes from device with address 0x3a
i2c.writeto(0x3a, '12') # write '12' to device with address 0x3a
buf = bytearray(10) # create a buffer with 10 bytes
i2c.writeto(0x3a, buf) # write the given buffer to the peripheral
SSD1306 Display mit I2C benutzen
Auf unserem Demoboard befindet sich ein Display mit einem SSD1306 IC das über den I2C angesteuert wird. Auf dem Board ist SCL = GPIO21 SDA = GPIO20. Das entspricht nicht der default Belegung, aber ist eine mögliche Belegung für I2C0.
Eine Display Instanz erstellen
Wenn wir ein I2C Display nutzen wollen, so müssen wir zuerst eine I2C Instanz erstellen. Da wir nicht die default Pin nutzen, müssen wir die Pins angeben.
Anschließend können wir eine SSD1306_I2C Instanz erzeugen. Damit haben wir jetzt Zugriff auf das Display.
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
i2c = I2C(0, sda=Pin(20), scl=Pin(21))
oled = SSD1306_I2C(128, 64, i2c)
Texte ausgeben
Die Anzeige von Texten ist sehr einfach. Dafür gibt es die Methode .text(String, x, y, Farbe).
Wobei die Farbe hier 0 = Schwarz und 1 = Weiss sein kann.
Die x,y Koordinaten beziehen sich auf Pixel nicht auf Zeichen.
Der Ursprung liegt oben links.
Zuerst sollte der Bildschirm gelöscht werden: oled.fill(0)
Dann wird der Text in den Framebuffer geschrieben oled.text('Hallo', 10, 10, 1)
Schließlich wird der Framebuffer zum Display kopiert und so der Text angezeigt. oled.show()
oled.fill(0)
oled.text('Hallo', 10, 10, 1)
oled.show()
Grafische Ausgaben
Vorab noch eine Übersicht der Display Befehle:
oled.poweroff() # Display ausschalten. Der Inhalt bleibt erhalten
oled.poweron() # Display einschalten. Der Inhalt wird wieder angezeigt
oled.contrast(0) # dunkel
oled.contrast(255) # hell
oled.invert(1) # Display wird invertiert angezeigt
oled.invert(0) # Display normal
oled.show() # write the contents of the FrameBuffer to display memory
Hier die von framebuf geerbten Funktionen:
oled.fill(0) # Der ganze Schirm wird mit der Farbe=0 gefüllt.
oled.pixel(0, 10) # Hole den Pixelwert von x=0, y=10
oled.pixel(0, 10, 1) # Setze den Pixelwert vonx=0, y=10 auf Farbe=1
oled.hline(0, 8, 4, 1) # Zeichne eine horizontale Line ab x=0, y=8, Länge=4, Farbe=1
oled.vline(0, 8, 4, 1) # Zeichne eine vertikale Line ab x=0, y=8, Länge=4, Farbe=1
oled.line(0, 0, 127, 63, 1) # Zeichne eine Linie von 0,0 to 127,63 - 6.Parameter: f=gefüllt ist True
oled.rect(10, 10, 107, 43, 1) # Zeichne eine rechteckige Umrandung von 10,10 nach 117,53, Farbe=1
oled.fill_rect(10, 10, 107, 43, 1) # Zeichne ein gefülltes Rechteck von 10,10 nach 117,53, Farbe=1
oled.text('Hello World', 0, 0, 1) # Zeichne Text ab x=0, y=0, Farbe=1
oled.scroll(20, 0) # Scrolle 20 Pixel nach rechts
oled.ellipse(x, y, xr, yr, c[, f, m])# Zeichne eine Ellipse, x,y=Mittelpunkt, xr, yr=die beiden Radien, c=Farbe
# [f=gefüllt ist True, m=Quadrant( Bit 0 Q1, b1 Q2, b2 Q3 und b3 Q4)]
oled.poly(x, y, coords, c[, f]) # Zeichne ein geschlossenes Polygon. x,y=Startpunkt, coords=Array mit Punkten relativ, c= Farbe
# [, f=gefüllt ist True]
# import array
# coords = array.array('h', [10,10, 20,-20, 30,30])
Als Beispiel für eine grafische Ausgabe habe ich das Micropython-Symbol heraus gegriffen:
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
i2c = I2C(0, sda=Pin(20), scl=Pin(21))
oled = SSD1306_I2C(128, 64, i2c)
oled.fill(0)
oled.fill_rect(0, 0, 32, 32, 1)
oled.fill_rect(2, 2, 28, 28, 0)
oled.vline(9, 8, 22, 1)
oled.vline(16, 2, 22, 1)
oled.vline(23, 8, 22, 1)
oled.fill_rect(26, 24, 2, 4, 1)
oled.show()
Die Praxis
Hier eine kleine Demo ():
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
i2c = I2C(0, sda=Pin(20), scl=Pin(21))
oled = SSD1306_I2C(128, 64, i2c)
oled.fill(0)
oled.text("Attraktor e.V.", 10, 0)
oled.text("Hello World!", 16, 32)
oled.show()
Hier ein etwas umfangreicheres Beispiel mit grafischen Elementen:
from machine import Pin, I2C
from ssd1306 import SSD1306_I2C
i2c = I2C(0, sda=Pin(20), scl=Pin(21))
oled = SSD1306_I2C(128, 64, i2c)
oled.fill(0)
oled.fill_rect(0, 0, 32, 32, 1)
oled.fill_rect(2, 2, 28, 28, 0)
oled.vline(9, 8, 22, 1)
oled.vline(16, 2, 22, 1)
oled.vline(23, 8, 22, 1)
oled.fill_rect(26, 24, 2, 4, 1)
oled.text('MicroPython', 40, 0, 1)
oled.text('SSD1306', 40, 12, 1)
oled.text('OLED 128x64', 40, 24, 1)
oled.show()
Das SSD1306 IC
SSD1306 Datenblatt
SSD1306.py intern
Nun wollen wir einen Blick in das Modul ssd1306.py werfen:
# MicroPython SSD1306 OLED driver, I2C and SPI interfaces
from micropython import const
import framebuf
# register definitions
SET_CONTRAST = const(0x81)
SET_ENTIRE_ON = const(0xA4)
...
class SSD1306(framebuf.FrameBuffer):
def __init__(self, width, height, external_vcc):
...
self.init_display()
def init_display(self):
for cmd in (SET_DISP | 0x00, ... SET_DISP | 0x01)
self.write_cmd(cmd)
self.fill(0)
self.show()
def poweroff(self):
self.write_cmd(SET_DISP | 0x00)
def poweron(self):
self.write_cmd(SET_DISP | 0x01)
def contrast(self, contrast):
self.write_cmd(SET_CONTRAST)
self.write_cmd(contrast)
def invert(self, invert):
self.write_cmd(SET_NORM_INV | (invert & 1))
def show(self):
x0 = 0
...
self.write_data(self.buffer)
Die Klasse SSD1306 erbt von der Klasse framebuf. Alle Methoden aus framebuf stehen deshalb auch in SSD1306 zur Verfügung. Dazu später.
In der Klasse SSD1306 wird alles das definiert, was zur Steuerung des Displays benötigt wird. Das sind die Initialisierung des Displays, Das Ein- und Ausschalten, die Kontrasteinstellung, die inverse Darstellung und die Übertragung des Framebuffers zum Display. Das geschieht unabhängig von der verwendeten Schnittstelle. Die Methoden write_cmd() und write_data() stellen die Brücke zur Schnittstelle her. Diese sind in den Klassen SSD1306_I2C uns SSD1306_SPI definiert. Diese beiden Klassen erben die Klasse SSD1306 und somit die Steuerung des Displays.
class SSD1306_I2C(SSD1306):
def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False):
...
def write_cmd(self, cmd):
self.temp[0] = 0x80 # Co=1, D/C#=0
self.temp[1] = cmd
self.i2c.writeto(self.addr, self.temp)
def write_data(self, buf):
self.write_list[1] = buf
self.i2c.writeto(self.addr, self.write_list)
Das Modul framebuf
Das Modul framebuf ist im Lieferumfang von Micropython enthalten. Es ermöglicht es im Speicher ein Abbild der Displayausgabe vorzubereiten.
>>> import framebuf
>>> dir(framebuf)
['__class__', '__name__', '__dict__', 'FrameBuffer', 'FrameBuffer1', 'GS2_HMSB', 'GS4_HMSB', 'GS8',
'MONO_HLSB', 'MONO_HMSB', 'MONO_VLSB', 'MVLSB', 'RGB565']
>>> dir(framebuf.FrameBuffer)
['__class__', '__name__', '__bases__', '__dict__', 'blit', 'ellipse', 'fill', 'fill_rect', 'hline', 'line', 'pixel', 'poly',
'rect', 'scroll', 'text', 'vline']
>>>
Die Display Instanz erbt alle Methoden aus FrameBuffer, SSD1306 und SSD1306_I2C.
Bus Erweiterung
Die I2C-IC's werden über geräteinterne Adressen angesprochen. Meist lassen sich die IC's auf mehrere Adressen einstellen. Das reicht aber nicht immer. Bei unserem Display sind maximal 2 Adressen möglich. Diese Einstellmöglichkeit ist aber auf den Break-Out-Boards häufig nicht vorgesehen. Um bei der Adressierung flexibler sein zu können, gibt es ein IC, dass einen I2C-Bus auf maximal 8 Busse umsetzt. Dabei kann nur einer, oder auch mehrere der Ausgangsbusse aktiv sein.
Datenblatt TCA9548a
Navigation
Zurück zur "Micropython Kurs 2023 Teil 2" Startseite
Zurück zur "Micropython Kurs 2023" Startseite
Zurück zur Programmieren Startseite
Zurück zur Wiki Startseite
|