I2C Bus mit MCP23017 am Pico

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Hallo
versuche mit einem Raspberry Pi Pico einen MCP23017 zu betreiben. Das schalten von LEDs am Port funktioniert bereits super. Leider habe ich Probleme den Zustand von Tastern auszulesen. Verwende diesen Code dazu:

Code: Alles auswählen

from machine import Pin,I2C
import utime
# Initialisierung I2C, Bus 0, sda-0, scl-1, Adresse 0x22, Frequenz 400kHz
SCL_Pin = 1 # Angabe Pin SCL
SDA_Pin = 0 # Angabe Pin SDA
Bus = 0     # Angabe Bus Nr
MCP_Address = 0x22 # MCP23017 Angabe der Bus Adresse - 0x22, sonst ermitteln
i2c = I2C(Bus, scl = Pin(SCL_Pin), sda = Pin(SDA_Pin), freq = 400000)

MCP_GPIOA = 0x12     # Spiegelt den Wert am Anschluss A wider.
MCP_GPIOB = 0x13     # Spiegelt den Wert am Anschluss B wider.

MCP_OLATA = 0x14     # Schalte Ausgänge Port A.
MCP_OLATB = 0x15     # Schalte Ausgänge Port B.

MCP_IODIRA = 0x00    # Steuert die Richtung der Daten-E/A für Anschluss A.
MCP_IODIRB = 0x01    # Steuert die Richtung der Daten-E/A für Anschluss B.

MCP_IPOLA = 0x02     # Konfiguriert die Polarität der entsprechenden GPIO-Port-Bits für Port A.
MCP_IPOLB = 0x03     # Konfiguriert die Polarität der entsprechenden GPIO-Port-Bits für Port B.

MCP_GPINTENA = 0x04  # Steuert den Interrupt-on-change für jeden Pin von Anschluss A.
MCP_GPINTENB = 0x05  # Steuert den Interrupt-on-change für jeden Pin von Anschluss B.

MCP_DEFVALA = 0x06   # Steuert den Standard-Vergleichswert für Interrupt-on-Change für Anschluss A.
MCP_DEFVALB = 0x07   # Steuert den Standard-Vergleichswert für Interrupt-on-Change für Anschluss B.

MCP_INTCONA = 0x08   # Steuert, wie der zugehörige Pin-Wert für den Interrupt-on-change für Anschluss A verglichen wird.
MCP_INTCONB = 0x09   # Steuert, wie der zugehörige Pin-Wert für den Interrupt-on-change für Anschluss B verglichen wird.

MCP_IOCON = 0x0A     # Steuert das Gerät

MCP_GPPUA = 0x0C     # Schaltet Pull-up-Widerstände für Port A auf 5V
MCP_GPPUB = 0x0D     # Schaltet Pull-up-Widerstände für Port B auf 5V

MCP_INTFA = 0x0E     # Spiegelt den Unterbrechungszustand an den Pins von Anschluss A wider
MCP_INTFB = 0x0F     # Spiegelt den Unterbrechungszustand an den Pins des Anschlusses B wider

MCP_INTCAPA = 0x10   # Erfasst den Wert von Anschluss A zum Zeitpunkt des Auftretens der Unterbrechung
MCP_INTCAPB = 0x11   # Erfasst den Wert des Anschlusses B zum Zeitpunkt des Auftretens der Unterbrechung

# Achtung Angabe der Pins in Hex
confA = [MCP_IODIRA, 0x00]  # Steuert die Richtung der Daten am Port A, 0 Ausgang, 1 Eingang
confB = [MCP_IODIRB, 0xff]  # Steuert die Richtung der Daten am Port B, 0 Ausgang, 1 Eingang
confC = [MCP_GPPUB, 0xff]   # Schaltet Pull-up-Widerstände für Port B auf 5V
confD = [MCP_GPIOB, 0xff]   # Spiegelt den Wert am Anschluss B wider

buff1 = [MCP_OLATA, 0x50]  # Angabe Zahl, Pins an nach Tabelle
buff0 = [MCP_OLATA, 0x00]  # Angabe 0, alle Pins aus
buff2 = [MCP_OLATA, 0x05]  # Angabe Zahl, Pins an nach Tabelle
buff3 = [MCP_OLATA, 0x03]  # Angabe Zahl, Pins an nach Tabelle

i2c.writeto(MCP_Address, bytearray(confA)) # Port A als Ausgang
i2c.writeto(MCP_Address, bytearray(confB)) # Port B als Eingang
i2c.writeto(MCP_Address, bytearray(confC)) # Schaltet Pull-up-Widerstände für Port B auf 5V
i2c.writeto(MCP_Address, bytearray(confD)) # Spiegelt den Wert am Anschluss B wider.

while True: # Beginn der Schleife
    
    abt = i2c.readfrom(MCP_Address ,8)
    
    print(abt) # Anzeige zur Kontrolle
    print()
    if (abt == 8):
        i2c.writeto(MCP_Address, bytearray(buff2)) # schreibt an die Adresse buff 1 - 1 2
        utime.sleep(0.5) # Pause
    else:
        i2c.writeto(MCP_Address, bytearray(buff3)) # schreibt an die Adresse buff 0 - 0 0
        utime.sleep(0.5) # Pause
Im gesamten Code verwende ich INT nicht. Die Register habe ich hier zum Beginn des Programmes für eine bessere übersicht drin.
Das auslesen des gedrückten Taster und zur Kontrolle das schalten einer LED funktioniert nicht. Alle Pins des Ports A liegen auf 5V und haben beim betätigen der Taster GND. Taster schalten nach GND.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du hast schon so nette Konstanten. Aber benutzt die nicht. Wenn du das getan haettest, waere dir vielleicht aufgefallen, das du von Port A liest, obwohl du den zum Ausgang konfiguriert hast. Und nicht von Port B.
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Bist du dir sicher mit deiner Antwort? Habe das so eingestellt:

Code: Alles auswählen

confA = [MCP_IODIRA, 0x00]  # Steuert die Richtung der Daten am Port A, 0 Ausgang, 1 Eingang
confB = [MCP_IODIRB, 0xff]  # Steuert die Richtung der Daten am Port B, 0 Ausgang, 1 Eingang
confC = [MCP_GPPUB, 0xff]   # Schaltet Pull-up-Widerstände für Port B auf 5V
confD = [MCP_GPIOB, 0xff]   # Spiegelt den Wert am Anschluss B wider
Mit A setze ich Port A auf Ausgang, mit B setze ich B auf Eingang. Mit C und D schalte ich die 5V und spiegel das Ergebniss.

Code: Alles auswählen

i2c.writeto(MCP_Address, bytearray(confA)) # Port A als Ausgang
i2c.writeto(MCP_Address, bytearray(confB)) # Port B als Eingang
i2c.writeto(MCP_Address, bytearray(confC)) # Schaltet Pull-up-Widerstände für Port B auf 5V
i2c.writeto(MCP_Address, bytearray(confD)) # Spiegelt den Wert am Anschluss B wider.
Mit dieser Angabe rufe ich die Register einmalig auf.
Habe zur Kontrolle einiges da von ausgeschaltet, es wird alles so gemacht wie es angegeben ist.
Damit führe ich einen Vergleich aus. Die LEDs danach habe ich zur Kontrolle drin. Der Wechselblinker funktioniert.

Code: Alles auswählen

while True: # Beginn der Schleife
    
    abt = i2c.readfrom(MCP_Address ,8)
    
    print(abt) # Anzeige zur Kontrolle
    print()
    if (abt == 0):
        i2c.writeto(MCP_Address, bytearray(buff2)) # schreibt an die Adresse buff 1 - 1 2
        utime.sleep(0.5) # Pause
    else:
        i2c.writeto(MCP_Address, bytearray(buff3)) # schreibt an die Adresse buff 0 - 0 0
        utime.sleep(0.5) # Pause
       
    # Kontrolle mit LED 
    i2c.writeto(MCP_Address, bytearray(buff1)) # schreibt an die Adresse buff 1 - 1 
    utime.sleep(0.5) # Pause
    i2c.writeto(MCP_Address, bytearray(buff0)) # schreibt an die Adresse buff 0 - 0
    utime.sleep(0.5) # Pause  
    
Es funktioniert das auslesen bzw der Vergleich des Tasters nicht.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich weiss nicht, was es jetzt nochmal bringen soll, deinen Code zu paraphrasieren. Hast du mal probiert nachzuvollziehen, worueber ich rede?

Code: Alles auswählen

    abt = i2c.readfrom(MCP_Address ,8)
Adresse 8. Laut deinem eigenen Code

Code: Alles auswählen

MCP_INTCONA = 0x08 
bezieht sich das auf Port A. Den du als Ausgabeport setzt, siehe confA. Einlesen willst du aber von Port B, siehe confB. Und darauf bezog sich meine Aussage. Wenn du von B lesen willst, warum benutzt du ein Register von A?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nachtrag: wenn ich das Datenblatt richtig verstehe (das ich wirklich nur grob ueberflogen habe), dann willst du Register MCP_GPIOB lesen. Aber bleibt bei meinem Argument. Du benutzt nicht das richtige Register.
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Irgendwie reden wir aneinander vorbei.

Code: Alles auswählen

MCP_IODIRA = 0x00    # Steuert die Richtung der Daten-E/A für Anschluss A.
MCP_IODIRB = 0x01    # Steuert die Richtung der Daten-E/A für Anschluss B.
Das ist das Register 0x00/01 mit Namen und steuert die Richtung der Daten

Code: Alles auswählen

MCP_INTCONA = 0x08   # Steuert, wie der zugehörige Pin-Wert für den Interrupt-on-change für Anschluss A verglichen wird.
MCP_INTCONB = 0x09   # Steuert, wie der zugehörige Pin-Wert für den Interrupt-on-change für Anschluss B 
Name Register, Zur Interrupt Steuerung, verwende ich nicht

Code: Alles auswählen

confA = [MCP_IODIRA, 0x00]  # Steuert die Richtung der Daten am Port A, 0 Ausgang, 1 Eingang
confB = [MCP_IODIRB, 0xff]  # Steuert die Richtung der Daten am Port B, 0 Ausgang, 1 Eingang
confC = [MCP_GPPUB, 0xff]   # Schaltet Pull-up-Widerstände für Port B auf 5V
confD = [MCP_GPIOB, 0xff]   # Spiegelt den Wert am Anschluss B wider
Das MCP_IODORA (0x00) ist das Register für Port A das die Datenrichtung bestimmt. Mit der Hex werden die Pins der I/O festgelegt. Damit man unterschiedliche I/O, halt gemischter Betrieb. Damit ist confA das Register 0x00 mit dem Wert 0x00 und schaltet den Port A von A0 bis A7 auf Ausgang.
Mit MCP_confB schalte ich das Register B (0x01) mit dem Wert alle auf 1 (0xff für alle) auf Eingang.

Code: Alles auswählen

i2c.readfrom_into(MCP_Address, data, 21)
Habe es mal so getestet. Die Daten stehen im bytearray data drin. An der 21 Stelle kann ich sehen wie das Pin schaltet (wackelt).

Code: Alles auswählen

i2c.writeto(MCP_Address, bytearray(confA)) # Port A als Ausgang
i2c.writeto(MCP_Address, bytearray(confB)) # Port B als Eingang
i2c.writeto(MCP_Address, bytearray(confC)) # Schaltet Pull-up-Widerstände für Port B auf 5V
i2c.writeto(MCP_Address, bytearray(confD)) # Spiegelt den Wert am Anschluss B wider.
Damit schalte ich z.B. die Busadresse 0x22, das Register 0x00 auf den Wert 0x00.
Im Slave 0x22 wird das Register 0x00 auf 0x00 geschaltet.
Habe es getestet, wenn ich dort 0xff eingebe schaltet der Ausgang nicht mehr.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und was hat das jetzt mit was zu tun? Du schreibst doch selbsts

Code: Alles auswählen

 i2c.writeto(MCP_Address, bytearray(confA)) # Port A als Ausgang
i2c.writeto(MCP_Address, bytearray(confB)) # Port B als Eingang
Heißt das nun du willst von Port B lesen? Ja oder Nein? Und wo in deinem Code steht jetzt deiner Meinung nach die Abfrage der Bits von PortB?

Das readfrom /writeto ist wahrscheinlich auch eher falsch. Weil für die Register-Adressierung (lesend) eigentlich erst ein Write zum Register erfolgen muss. Und dann ein read des Wertes. Du ballerst aber einfach arrays raus, und liest welche ein, die dann aber so behandelt werden, als ob sie ein Registerwert wären.
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Leider scheint Python ein bischen anders zu sein.
Mit dem erst schreiben hast du vollkommen Recht. Bei Atmega hab ich das so genacht und es funktioniert alles. Kenn es auch so, das auf die Adresse 0x22 geschrieben wird und auf 0x22+1 gelesen wird. Wenn man im Netz sucht findet man Angaben und kurze Hinweise wie man das macht oder machen sollte. Zu diesem erst schreiben und dann lesen steht nichts. Den PCF8574 habe ich die Abfrage ohne Problem geschafft. Ist aber ein einfacher IC. Der MCP ist da schon etwas anderes. Das mit dem erst schreiben und dann lesen werde ich testen, Danke für deinen Hinweis.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Es gibt dafür schon Methoden. Musst mal die Dokumentation von micropython lesen. Da gibts welche, die nehmen Geräteadresse und Register, und lesen oder schreiben das.
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Morgen
genau diese meine ich, bin danach gegangen. Im Netz und den Fachbüchern findet man ja auch Beispiel zum Code. Meist aber nur wie man eine LED schaltet. Abfrage eines Taster habe ich bisher nicht gefunden.
Wenn ich das genauso mache wie beim AVR habe ich ein bischen Angst um den Pico. So einfach den Ausgang auf H schalten und den Taster auf GND - da könnte doch der Aush^gang sich das leben nehmen?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wieso sollte das nicht gehen? Was am Pico ist anders als am AVR, und wieso hast du keine Angst um deine I2C Pins am Pico? Die werden doch genauso belastet? Ist I2C irgendwie magisch besser?

Ich finde jede Menge Beispiele zum einlesen von Daten, zb https://tronixstuff.com/2011/08/26/ardu ... -tutorial/ etc PP. Wichtig auch hier die korrekte Ansprache der Register. I2C ist nicht trivial.
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Habe mir den Vergleich zum PCF8574 angesehen, ist doch unterschiedlich:

Code: Alles auswählen

if (PCF_Adresse_2.pin(1) == 0):
       PCF_Adresse_1.pin(4, 1) # LED an 0
       PCF_Adresse_1.pin(5, 0) # LED an 0
       lcd.move_to(6,2)                   # Gehe zu Spalte/Zeile
       lcd.putstr('gedr\365ckt      ') # Angabe Text \365 Sonderzeichen für ü
        
    else:
       PCF_Adresse_1.pin(4, 0) # LED aus 1
       PCF_Adresse_1.pin(5, 1) # LED an 0
       lcd.move_to(6,2)                   # Gehe zu Spalte/Zeile
       lcd.putstr('nicht gedr\365ckt') # Angabe Text \365 Sonderzeichen für ü
Das Programm verwendet die Lib pcf8574.py von causer. Damit ist es relativ leicht 3 verschiedne Slave am Bus zu betreiben. Es braucht ja auch keine Registereinstellungen. Werde als nächste mal die Einbindung der Lib vom mcp23017.py angehen. Da beide Libs vom gleichen Autor stammen müsste es gehen.
Habe mir deine Angabe zum MCP angesehen. Es wird c/c++ verwendet vom Arduino. Ist wieder etwas anders. Der Weg ist so ziemlich alles das selbe.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das der Code in C++ ist, ist doch irrelevant. Du siehst die genutzten I2C-Instruktionen, und dafuer sollte die Abstraktion doch hoffentlich reichen. Also musst du die dann nur abbilden in deinem Code.
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Habe nach der Doku von Python und der Hinweise im Netz so ziemlich alles ausprobiert was möglich. Siehe da, es funktioniert was:

Code: Alles auswählen

while True: # Beginn der Schleife
    data=i2c.readfrom_mem(MCP_Address, MCP_GPIOB, 1)
    print(data) # Anzeige zur Kontrolle am PC
    if data == b'\xfe':
        i2c.writeto(MCP_Address, bytearray(buff2)) # schreibt an die Adresse buff 1 - 1 ,2
        utime.sleep(0.5) # Pause
    else:
        i2c.writeto(MCP_Address, bytearray(buff3)) # schreibt an die Adresse buff 0 - 0 ,3
        utime.sleep(0.5) # Pause
Damit erfolgt die Auswertung des Tasters an B0. Der Rückgabe Wert von MCP_GPIO ist b'\xff. Wenn ich den Taster B0 betätige erscheint b'\xfe und die if wird ausgeführt.
Jetzt habe ich das Problem, was ist b'\xfe ???
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Da du high pullst, 8 mal, hast du 0xff. Dann drueckst du deinen Taster, und du toggelst ein Bit, augenscheinlich Bit 0. Also hast du 0xfe, oder binaer 0b11111110. So wie es bei so Bitfummelei eben ist. Wenn du nur auf ein Bit testen willst, musst du eben die Verknuepfung entsprechend machen. Zb

Code: Alles auswählen

position = 0 # oder eben 1-7
if (data ^ 0xff) & (1 << position):  # erst Bits invertieren, dann auf gesetztes Bit pruefen.
   tuwas()
Aber da gibt' natuerlich auch beliebig viele andere Moeglichkeiten, zB

Code: Alles auswählen

if not (data & (1 << position)): # auf geloeschtes Bit pruefen
wobei ich so Negationen vermeide.
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Bist du dir sicher das der Code funktioniert. Habe ihn (hoffentlich) richtig eingegeben:

Code: Alles auswählen

data=i2c.readfrom_mem(MCP_Address, MCP_GPIOB, 1) 
    print(data) # Anzeige zur Kontrolle am PC
    position = 0 # oder eben 1-7
    if (data ^ 0xff) & (1 << position):  # erst Bits invertieren, dann auf gesetztes Bit pruefen.       
        i2c.writeto(MCP_Address, bytearray(buff2)) # schreibt an die Adresse buff 1 - 1 ,2
        utime.sleep(0.5) # Pause
    else:
        i2c.writeto(MCP_Address, bytearray(buff3)) # schreibt an die Adresse buff 0 - 0 ,3
        utime.sleep(0.5) # Pause
damit bekomme ich diese Fehlermeldung:

Traceback (most recent call last):
File "<stdin>", line 102, in <module>
TypeError: unsupported types for __xor__: 'bytes', 'int'

Meine damit das ganze: b'\xff , nicht bloss xff'
Sirius3
User
Beiträge: 17793
Registriert: Sonntag 21. Oktober 2012, 17:20

Natürlich mußt Du das Byte in eine Zahl umwandeln, um damit rechnen zu können:

Code: Alles auswählen

    if (data[0] ^ 0xff) & 1:
        i2c.writeto(MCP_Address, bytearray(buff2)) # schreibt an die Adresse buff 1 - 1 ,2
    else:
        i2c.writeto(MCP_Address, bytearray(buff3)) # schreibt an die Adresse buff 0 - 0 ,3
    utime.sleep(0.5)
Achim Klein
User
Beiträge: 41
Registriert: Dienstag 21. Februar 2023, 13:57

Als erstes die gute Nachricht, es funktioniert. In dieser Zeile liegt demnach die Auswertung des Rückgabewertes:

Code: Alles auswählen

if (data[0] ^ 0xff) & 4:
Der gewünschte Taster liegt in der "4". Bei einer 1 geht der Taster 1, bei einer 2 der Taster 2, bei einer 3 der Taster 1 und 2, bei einer 4 der Taster 3. usw Soweit klar.
Könntest du mir auch erklären was du da (genau) machst? Sorry ist mir noch zu hoch.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

^ ist der xor operator. Ein Byte xored mit FF invertiert alle Bits. Und damit kannst du dann mit & auf ein Bit Testen.
Antworten