PCF8574AN auslesenen...

Antworten
Benutzeravatar
Maik81SE
Beiträge: 308
Registriert: Fr 30. Sep 2011, 14:07
OS, Lazarus, FPC: Debian 12 (L 3.0.0.3 FPC 3.2.2); Windows 10 (L 3.99.0.0 FPC 3.2.0)
CPU-Target: x86-64; arm; avr
Wohnort: Lübeck
Kontaktdaten:

PCF8574AN auslesenen...

Beitrag von Maik81SE »

Moin zusammen,

ich weiß der PCF8574AN ist mir einer der einfachsten I2C-ICs, den man programmieren kann, aber iwie stehe ich gerade total auf dem Schlauch, komme jedoch auch an keine alten Bespiele meines Gnublin-Boards an, wo ich lauffähige Programme hatte.

Laut diversen Foren und und Datenblättern soll man bei dem IC nach der Initialisierung dem Port die Funktion (Input/Output) zuweisen.

Soweit kann ich den Unterlagen auch folgen.
Dementsprechend habe ich auch gem. Datasheet meine "read-Sektion" aufgebaut.

Code: Alles auswählen

 program SerCom;

uses uart{, IO_Exp}, twi, test, uConst_v07_25122020;

{#TODO -uart (Erweitern auf µC mit 2 und mehr UARTS}
{#todo -IO_Exp aufbauen wenn alle TWI-Funkionen des PCF ohne Fehler laufen}
{#todo -Test = Sammelspielwiese für Allgemeine AVR funktionen (Pause, Portdefine, usw. Später Vernünftig aufbauen}
{#done -uConst; Globale Sammlung von Constanten (Plattformübergreifend}
{#todo - b := TWIReadACK * $100 + TWIReadNACK; anfassen da Bytes nicht direkt geschrieben können.}

 const

  WakeUp                          = #36; // '$'
  Trans_Start                     = #60; // '<'
  Trans_Sektor                    = #35; // '#'
  Trans_End                       = #62; // '>'
 
 var
   RXD                             : ShortString;
   a                               : byte;
   b                               : UInt16;

begin
  asm cli end;
  UARTInit;
  TWIInit;
  asm sei end;
  repeat
    a               := 0;
    if UARTReadChar = WakeUp then begin
      repeat
        RXD[a]      := UARTReadChar;
        inc(a);
      until RXD[a-1] = Trans_End;
      if ((RXD[0] = '0') and (RXD[1] = '>')) then begin
 
        UARTSendString('TWI in den Lesemodus setzen!'#13#10);
        TWIStart((PCF shl 1) or TWI_Write);       // <--- PCF = $38 + Write
        TWIWrite(1);                              // <--- ACK
        TWIWrite(%1111111);                       // <--- Alle Ports als Input
        TWIWrite(1);                              // <--- ACK
        TWIStop;
        TWIStart((PCF shl 1) or TWI_Read);        // <--- PCF + Read
        TWIWrite(1);                              // <--- ACK
        UARTSendString(#13#10'Warte auf Eingabe vom IO-Expander'#13#10);
        repeat
          b := TWIReadACK * $100 + TWIReadNACK;   // Erwarteter Wert (IntToStr wird aber nicht genommen)
          UARTSendChar(char(b));
          UARTSendChar('|');
          UARTSendChar(char(TWIReadACK+48));
          UARTSendString('-'#13#10);
          Delay(100000);
          until (UARTReadChar = #32);
        TWIStop;
        UARTSendString(#13#10'TWI wurde angehalten!'#13#10);
        end;
      end;

    until ((UARTReadChar = 'E') or (UARTReadChar = 'e'));
    UARTSendString('Und ich bin nun das Programmende!!!');
end.
Der Formhalber habe ich hier mal das komplette Testprogramm gelistet.

Betrieben wird mein ATmega16 mit 8MHz.
der PCF8574 ist auf 100KHz eingestellt.

Die Write Routine 0-255 habe ich raus genommen, da diese ohne Ärger läuft.

Code: Alles auswählen

label.caption:= 'gnublin.no-ip.info'
Debian 12 (L 3.0.0.3 FPC 3.2.2);
windows 10 (L 3.99.0.0 FPC 3.2.0)

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: PCF8574AN auslesenen...

Beitrag von Timm Thaler »

Und wo ist jetzt Deine Frage?

Der 8574 hat keine Ein-Ausgänge wie man sie von einem Controller kennt. Die Pins sind beim Start auf High mit einer Stromquelle (Pull-Up) die etwa 50µA liefert.

Du setzt die Pins auf 1. Dann bleibt die Stromquelle aktiv. Damit kannst Du einen Transistor durchschalten als Ausgang. Oder einen Schalter dranhängen gegen GND. Liest Du die Pins, bekommst Du eine 0 wenn Schalter gedrückt, oder eine 1 wenn Schalter offen.

Du setzt die Pins auf 0. Dann schaltet intern ein Transistor gegen GND durch. Damit kannst Du eine externe LED gegen 5V schalten, oder über einen PNP-Transistor nach 5V ein Relais. LED oder Relais sind dann low-aktiv. Der 50µA Strom aus der Stromquelle fließt ebenfalls. Liest Du die Pins, bekommst Du wieder 0 zurück.

Besonders doof ist, dass die Pins am Anfang auf High liegen. Dadurch schaltet ein Transistor oder ULN immer erstmal durch.

Eine bessere Alternative sind die MCP23008/23017. Richtige Ein/Ausgänge, deutlich weniger Stromverbrauch, aber auch aufwendiger anzusteuern.

Du weißt, dass der 8574A eine andere Adresse hat?

Hint: Dein AVR ist über diese langen Sendestrings nicht glücklich. Damit müllst Du Dir den RAM zu, weil der Compiler das leider alles am Anfang in den RAM kopiert und dann aus diesem liest.

Entweder kurze Strings, oder direkt aus dem Flash senden.

Benutzeravatar
Maik81SE
Beiträge: 308
Registriert: Fr 30. Sep 2011, 14:07
OS, Lazarus, FPC: Debian 12 (L 3.0.0.3 FPC 3.2.2); Windows 10 (L 3.99.0.0 FPC 3.2.0)
CPU-Target: x86-64; arm; avr
Wohnort: Lübeck
Kontaktdaten:

Re: PCF8574AN auslesenen...

Beitrag von Maik81SE »

Timm Thaler hat geschrieben:
Mi 30. Dez 2020, 04:21
Und wo ist jetzt Deine Frage?
Ob ich im Grunde bei der Adressierung bzw den Aufruf der Read-Sequenz einen Gravierenden Denkfehler habe, gerade weil die 8574-Reihe so "EINFACH" zu handeln ist, wenn man sich das Datasheet durchliest.
Besonders doof ist, dass die Pins am Anfang auf High liegen. Dadurch schaltet ein Transistor oder ULN immer erstmal durch.
das hab ich auch auf den Unterlagen entnommen und wenn ich das in folge dessen richtig interpretiere muß ich die IO bevor ich diese lesen kann, erst einmal komplett löschen,
Möglichst so, das ich diese Invers ihre Entfunktion arbeiten.

Sprich bei Low-Aktiv alle auf "L" und 2 x NOP später auf "H".
Somit wären eventuelle Störungen ausgeschloßen.
Eine bessere Alternative sind die MCP23008/23017. Richtige Ein/Ausgänge, deutlich weniger Stromverbrauch, aber auch aufwendiger anzusteuern.
Ich glaube ich sollte die Seite von TME, welche du geteilt hattest, sollte ich in meine Favoriten legen, bevor ich bestelle. :?
Du weißt, dass der 8574A eine andere Adresse hat?
Ja, das ist ein Punkt, den ich mir in meinen Definitionen genauer im Auge behalten muß.
Hint: Dein AVR ist über diese langen Sendestrings nicht glücklich. Damit müllst Du Dir den RAM zu, weil der Compiler das leider alles am Anfang in den RAM kopiert und dann aus diesem liest.

Entweder kurze Strings, oder direkt aus dem Flash senden.
Ja, eine scheiß Gewohnheit, ist aber für mich die schnellste Möglichkeit, Ausgaben zu sehen, ohne später die Programme aufräumen zu müssen, so brauch ich dies Zeilen nur löschen, bzw durch Ihren Eigentlichen Befehlssatz ersetzen.

Code: Alles auswählen

<x#y#z>

Code: Alles auswählen

label.caption:= 'gnublin.no-ip.info'
Debian 12 (L 3.0.0.3 FPC 3.2.2);
windows 10 (L 3.99.0.0 FPC 3.2.0)

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: PCF8574AN auslesenen...

Beitrag von Timm Thaler »

Maik81SE hat geschrieben:
Mi 30. Dez 2020, 11:29
das hab ich auch auf den Unterlagen entnommen und wenn ich das in folge dessen richtig interpretiere muß ich die IO bevor ich diese lesen kann, erst einmal komplett löschen,
Möglichst so, das ich diese Invers ihre Entfunktion arbeiten.
Nein, Vorsicht, da ist zwar ein Inverter drin (Schaltbild 7), aber der treibt die Ausgangstransistoren:

Data low => Inverter high => unterer NMOS geschaltet => Ausgang aktiv low
Data high => Inverter low => oberer PMOS heschaltet => Ausgang über Stromquelle high oder als Eingang verwendbar

Initial sind alle Pins high über Stromquelle, können also als Eingänge gelesen werden.

Die Pins werden mit dem ersten Read (Bit 0 in Adresse low) in das untere Flipflop gelesen und dann sofort im nächsten Byte gesendet.

Du kannst alle Pins high setzen, um einen sicheren Zustand zu erhalten. Aber wenn Du sie mit Write low setzt, wirst Du mit Read auch nur low lesen.

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: PCF8574AN auslesenen...

Beitrag von Timm Thaler »

Achso, eine beliebte Fehlerquelle bei I2C ist auch das Shift der Adresse zu vergessen. Also 7bit-Adresse plus W/R-Bit vs 8bit-Adresse mit inkludem W/R-Bit. Da manchen Hersteller die Adresse so, andere so angeben sorgt das mitunter für Verwirrung.

Benutzeravatar
Maik81SE
Beiträge: 308
Registriert: Fr 30. Sep 2011, 14:07
OS, Lazarus, FPC: Debian 12 (L 3.0.0.3 FPC 3.2.2); Windows 10 (L 3.99.0.0 FPC 3.2.0)
CPU-Target: x86-64; arm; avr
Wohnort: Lübeck
Kontaktdaten:

Re: PCF8574AN auslesenen...

Beitrag von Maik81SE »

Timm Thaler hat geschrieben:
Mi 30. Dez 2020, 14:36
Nein, Vorsicht, da ist zwar ein Inverter drin (Schaltbild 7), aber der treibt die Ausgangstransistoren:

Data low => Inverter high => unterer NMOS geschaltet => Ausgang aktiv low
Data high => Inverter low => oberer PMOS heschaltet => Ausgang über Stromquelle high oder als Eingang verwendbar

Initial sind alle Pins high über Stromquelle, können also als Eingänge gelesen werden.

Die Pins werden mit dem ersten Read (Bit 0 in Adresse low) in das untere Flipflop gelesen und dann sofort im nächsten Byte gesendet.

Du kannst alle Pins high setzen, um einen sicheren Zustand zu erhalten. Aber wenn Du sie mit Write low setzt, wirst Du mit Read auch nur low lesen.
wenn ich das entsprechend jetzt richtig verstehe.

<S - addr + write><ACK><data = $0FF> alles "H"<ACK><P> -> <S - addr + Read><ACK><read in ACK * $100 + NACK><NACK><P>

SO würde das jetzt auch analog aus dem Datasheet von zB
NXP
entnehmen um Daten zu Lesen.

Bin zudem schon am überlegen, folgende Procedure zu splitten

Code: Alles auswählen

procedure TWIStart(addr: byte);
begin
  TWCR        := 0;
  TWCR        := (1 shl TWINT) or (1 shl TWSTA) or (1 shl TWEN);
  while       ((TWCR and (1 shl TWINT)) = 0) do begin end;

  // Adresse des Endgerätes senden
  TWDR        := addr;
  TWCR        := (1 shl TWINT) or (1 shl TWEN);
  while       ((TWCR and (1 shl TWINT)) = 0) do begin end;
end;
da würde ich mir das Start und Stop jeweils sparen und könnte innerhalb zw Write/Read umschalten.

Bevor Fragen kommen, woher ich diese habe?

AVR Embedded Tutorial - I²C, TWI/de

Code: Alles auswählen

label.caption:= 'gnublin.no-ip.info'
Debian 12 (L 3.0.0.3 FPC 3.2.2);
windows 10 (L 3.99.0.0 FPC 3.2.0)

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: PCF8574AN auslesenen...

Beitrag von Timm Thaler »

Maik81SE hat geschrieben:
Mi 30. Dez 2020, 15:27
<S - addr + write><ACK><data = $0FF> alles "H"<ACK><P> -> <S - addr + Read><ACK><read in ACK * $100 + NACK><NACK><P>
Was auch immer das sein soll? Aber zumindest ist $0FF schonmal Quatsch. Wir senden 8 Bit oder 16 Bit, aber keine 12 Bit.

Und wenn ich das richtig interpretiere, sendest Du hier eine Adresse+Write und dann $0FF. $0FF wird zu $00 und $FF. Das erste Byte setzt alle Pins auf low und das zweite wird verworfen. Zack, und schon sind Deine Pins Ausgang und low und Du liest nur noch Nullen ein.

Und was das zweite $100 sein soll...

Hier ein Stück aus meiner Heizung:

Code: Alles auswählen

const
  Ciom_wr = %01001110;
  Ciom_rd = %01001111;  // Adresse 1-1-1 => Achtung 8574 ohne A

// Init
  twi_start();  // Start TWI
  twi_write(Ciom_wr);  // DevSel senden
  twi_write($FF);  // Daten senden, alle Eingänge Pull-up
  twi_stop();  // Stop TWI

// Daten senden
  twi_start();  // Start TWI
  twi_write(Ciom_wr);  // DevSel senden
  ackn := twi_write(data);  // Ausgänge setzen, Eingänge Pull-up
  twi_stop();  // Stop TWI
// wenn kein ackn zurück, Chip nicht vorhanden

// Daten lesen
    twi_start();  // Start TWI
    twi_write(Ciom_rd);  // DevSel senden
    data := twi_readbyte(false);  // Eingänge holen
    twi_stop();  // Stop TWI
Mit den Funktionen die Du oben verlinkt hast. Mehr isses nicht und viel mehr geht mit dem 8574 auch nicht.

Benutzeravatar
Maik81SE
Beiträge: 308
Registriert: Fr 30. Sep 2011, 14:07
OS, Lazarus, FPC: Debian 12 (L 3.0.0.3 FPC 3.2.2); Windows 10 (L 3.99.0.0 FPC 3.2.0)
CPU-Target: x86-64; arm; avr
Wohnort: Lübeck
Kontaktdaten:

Re: PCF8574AN auslesenen...

Beitrag von Maik81SE »

Timm Thaler hat geschrieben:
Mi 30. Dez 2020, 21:41
Maik81SE hat geschrieben:
Mi 30. Dez 2020, 15:27
<S - addr + write><ACK><data = $0FF> alles "H"<ACK><P> -> <S - addr + Read><ACK><read in ACK * $100 + NACK><NACK><P>
Was auch immer das sein soll? Aber zumindest ist $0FF schonmal Quatsch. Wir senden 8 Bit oder 16 Bit, aber keine 12 Bit.
Ok. das mit dem $0FF gut zu wissen, das FP so haben will. :) Vielleicht werde ich mir bei AVR-Programmen angewöhnen diese Parts eher Binär zu schreiben.
Auf jedenfall merkt man an diesen Kleinigkeiten den Unterscheid zu C++ :(
Hier ein Stück aus meiner Heizung:

Code: Alles auswählen

const
  Ciom_wr = %01001110;
  Ciom_rd = %01001111;  // Adresse 1-1-1 => Achtung 8574 ohne A

// Init
  twi_start();  // Start TWI
  twi_write(Ciom_wr);  // DevSel senden
  twi_write($FF);  // Daten senden, alle Eingänge Pull-up
  twi_stop();  // Stop TWI

// Daten senden
  twi_start();  // Start TWI
  twi_write(Ciom_wr);  // DevSel senden
  ackn := twi_write(data);  // Ausgänge setzen, Eingänge Pull-up
  twi_stop();  // Stop TWI
// wenn kein ackn zurück, Chip nicht vorhanden

// Daten lesen
    twi_start();  // Start TWI
    twi_write(Ciom_rd);  // DevSel senden
    data := twi_readbyte(false);  // Eingänge holen
    twi_stop();  // Stop TWI
Mit den Funktionen die Du oben verlinkt hast. Mehr isses nicht und viel mehr geht mit dem 8574 auch nicht.
Das schaut schon mal eher danach aus.
Ich bin meinen Recherchen auch oft über das AVR Embedded Tutorial - Software I2C, TWI gestoßen.
Da ich aber OHNE Arduino arbeite habe ich dem keine weitere Beachtung geschenkt...
aber wenn ich mir das auf den ersten Blick durchlese, kann ich mir dies einfach mit meinen ATmega16A/ATmega128 Ports/Register umschreiben.

EDIT
Mega Dank Timm Thaler,

Nun schaut es um längen besser aus.
Läuft für meine beiden PCF8574AN sehr sehr gut.

Code: Alles auswählen

label.caption:= 'gnublin.no-ip.info'
Debian 12 (L 3.0.0.3 FPC 3.2.2);
windows 10 (L 3.99.0.0 FPC 3.2.0)

Timm Thaler
Beiträge: 1224
Registriert: So 20. Mär 2016, 22:14
OS, Lazarus, FPC: Win7-64bit Laz1.9.0 FPC3.1.1 für Win, RPi, AVR embedded
CPU-Target: Raspberry Pi 3

Re: PCF8574AN auslesenen...

Beitrag von Timm Thaler »

Maik81SE hat geschrieben:
Mi 30. Dez 2020, 22:05
Ich bin meinen Recherchen auch oft über das AVR Embedded Tutorial - Software I2C, TWI gestoßen.
Da ich aber OHNE Arduino arbeite habe ich dem keine weitere Beachtung geschenkt...
aber wenn ich mir das auf den ersten Blick durchlese, kann ich mir dies einfach mit meinen ATmega16A/ATmega128 Ports/Register umschreiben.
Die Software-TWI-Routinen stammen von mir, ich verwende die auf einem ATmega32, jetzt ATmega1284 - das Display-Menu braucht unheimlich Flash ;-) - für meine Heizung und auf diversen ATmega328 für I2C-Sensoren (BME280, HTU21...). Die sollten auf so ziemlich jeden ATmega oder ATtiny laufen.

Software-TWI deswegen, weil man dann die I2C-Pins beliebig verteilen kann. So hab ich für die Gewächshaussteuerung 2 I2C-Busse, weil der blöde HTU21 nicht adressierbar ist ich aber 2 Stück brauche. Oder alle 8 ADCs, der Hardware-TWI am 328 aber auf den ADC-Pins liegt.

Dass das unter Arduino steht liegt an Matthias, er kommt aus der Arduino-Ecke und hat da viel beigetragen.

Benutzeravatar
Maik81SE
Beiträge: 308
Registriert: Fr 30. Sep 2011, 14:07
OS, Lazarus, FPC: Debian 12 (L 3.0.0.3 FPC 3.2.2); Windows 10 (L 3.99.0.0 FPC 3.2.0)
CPU-Target: x86-64; arm; avr
Wohnort: Lübeck
Kontaktdaten:

Re: PCF8574AN auslesenen...

Beitrag von Maik81SE »

Stimmt...

Nur weil es unter Adruino liegt, heißt es ja nicht, daß mab dies nicht auch ohne benutzen kann...

Werde wenn meine ATtiny25 und mein ATMega128 da sind dies mal versuchen universal anzupassen.
Ggf als AVR-Package oder Unit Sammlung

Code: Alles auswählen

label.caption:= 'gnublin.no-ip.info'
Debian 12 (L 3.0.0.3 FPC 3.2.2);
windows 10 (L 3.99.0.0 FPC 3.2.0)

Antworten