MCP23S18 I/O Expander

Gast #6134985
Lesenswert?

Hallo miteinander,

Ich komme irgendiwie an meine Grenzen mit dem Verständlichen eines 
Datenblattes. Auch wen ich es noch so ganz genau anschaue sehe ich da 
nicht wirklich durch. Vieleicht hat auch schon jemand mit diesem 
Baustein (I/O Expander) MCP23S18 erfahrungen gemacht. Alles was ich 
möchtes ist lediglich An PORTA etwas ausgeben also z.B. 0x04. Schon 
alleine die Register Adressen sind nirgends erklärt da bin ich aber 
soweit das ich das Register OLATA benötige um ein Ausgangssignal zu 
generieren. Aber schon das zweite mit der BANK 1/0 ist nicht wirklich 
erleutert wie ich das setze. Würde ich dies mit I2C programmieren wäre 
es ev. einfacher da gibt es ein wunderbares Flussdiagramm aber bei SPI 
nicht. Sie sagen man sollte auch das definieren ob es jezt ein Eingang 
oder ein Ausgang (DDR) ist, aber das kann ich auch nicht mehr entnehmen 
von dem Ablauf her was zuerst kommt. Was mir auch klar ist das ich mit 
0x40 schreiben und mit 0x41 lesen kann. Und wen ich jetzt das so 
anschaue müsste ich für meinen Zweck 0x40(Schreiben), 0x14 Regeister 
Adresse PortA, 0x04 (Pin 3 output 3,3V) senden.
Ev. könnte mir jemand auf die Sprünge helfen oder sieht gleich wiso ich 
so nicht durchblicke. Leider habe ich auch noch keine Legende entdeckt 
über all diese Abkürzungen der Register.

Danke.
Angehängte Dateien:
Gast #6135014
Lesenswert?

Nico F. schrieb:
> alleine die Register Adressen sind nirgends erklärt

Doch im Datenblatt das Du angehängt hast, mußt Du nur genau lesen.

Du entscheidest Dich zuerst, ob Du zwei 8Bit Ports oder einen 16Bit-Port 
haben willst. Dementsprechend setzt Du Bit 7 (Bank) in IOCON 
(0x05/0x15).
Dadurch hast Du die Adressen der anderen Register festgelegt.
Dann legst Du in IODIRA oder IODIRB (je nach Port) fest welcher Pin Ein- 
oder Ausgang sein soll.
Danach kannst Du GPIOA oder GPIOB schreiben oder lesen. Wenn Du es liest 
bekommst Du den aktuellen Zustand, wenn Du schreibst wird OLATA/B 
entsprechend gesetzt. Du kannst auch OLATA/B direkt beschreiben.

Der Rest kommt später ;-)
#6135033
Lesenswert?

Hi, also ich nutze den 23S17, der ist ganz ähnlich.

Die Register sind doch ab Seite 18 im Datenblatt sehr genau beschrieben. 
Du musst doch bloß im IODIR das entsprechende Muster reinschreiben, 
damit die Pins Ausgang werden (Standard ist Input) und dann in GPIO das, 
was du ausgeben willst.

Da das BANK-Bit beim Starten 0 ist, sind die Register abwechselnd 
gemappt, wie du in Tabelle 1-1 sehen kannst. Also IODIR A/B auf 0x00 und 
0x01 und GPIO auf 0x12 und 0x13.

So sehen meine Routinen zum Beschreiben und Auslesen aus:
1
void Write23s17(uint8_t reg, uint8_t data) {
2
    SPITXbuffer[0] = 0x40;  // 4 is fix, 0 is SPI address #0 with R/W=0
3
    SPITXbuffer[1] = reg;   // register address follows
4
    SPITXbuffer[2] = data;  // data byte follows
5

6
    // Select MCP23S17
7
    CS_IO_EXPANDEROff();
8
    
9
    Write_Buffer_Handle4 = DRV_SPI_BufferAddWriteRead(SPIHandle4,
10
                (SPI_DATA_TYPE *) &SPITXbuffer[0], 3,
11
                (SPI_DATA_TYPE *) &SPIRXbuffer[0], 3,
12
                NULL,NULL
13
                );
14
    // Wait for transmission done
15
    while (0 == (DRV_SPI_BufferStatus(Write_Buffer_Handle4) & DRV_SPI_BUFFER_EVENT_COMPLETE));
16
    
17
    CS_IO_EXPANDEROn();
18
}
19

20
uint8_t Read23s17(uint8_t reg) {
21
    SPITXbuffer[0] = 0x41;  // 4 is fix, 1 is SPI address #0 with R/W=1
22
    SPITXbuffer[1] = reg;
23
    
24
    // Select MCP23S17
25
    CS_IO_EXPANDEROff();
26
    
27
    Write_Buffer_Handle4 = DRV_SPI_BufferAddWriteRead(SPIHandle4,
28
                (SPI_DATA_TYPE *) &SPITXbuffer[0], 2,
29
                (SPI_DATA_TYPE *) &SPIRXbuffer[0], 3,
30
                NULL,NULL
31
                );
32
    // Wait for transmission done
33
    while (0 == (DRV_SPI_BufferStatus(Write_Buffer_Handle4) & DRV_SPI_BUFFER_EVENT_COMPLETE));
34
    
35
    CS_IO_EXPANDEROn();
36
    
37
    return SPIRXbuffer[2];
38
}

Viele Grüße

Jochen
#6135135
Lesenswert?

Hallo,

hmmm, die Register sind eigentlich alle im Datenblatt auf den letzten 
Seiten erklärt. Klar, da muss man sich durchbeißen. Die Register sind 
ebenfalls gleich zu meinem MCP23017 (I2C). So sieht der Auszug meiner 
Klasse für Ausgänge aus. Die Methoden readOneRegister und 
writeOneRegister sind nur die I2C Transfer Funktionen. Musste nur durch 
SPI Transfer Funktionen ersetzen. i2cAdr wäre dann bei dir der SPI /CS 
Pin.

Die Pinnummern des MCP habe ich für mich einfach durchnummeriert und 
übergebe diesen Wert bei der Objektinitialisierung.
1
#pragma once
2

3
// Register Table 3.1 mit IOCON.BANK = 0
4
constexpr uint8_t IODIRA   = 0x00;
5
constexpr uint8_t IODIRB   = 0x01;
6
constexpr uint8_t OLATA    = 0x14;
7
constexpr uint8_t OLATB    = 0x15;
8

9

10
// GPA.0 ... GPA.7 => Pinnummer 0-7
11
// GPB.0 ... GPB.7 => Pinnummer 8-15  
12
  
13
// *** interne Hilfsfunktionen *** //
14
constexpr uint8_t getBitMaske(const uint8_t pin) {
15
  return (pin<8) ? (1<<pin) : (1<<(pin-8));
16
}
17

18
constexpr uint8_t get_DIR_Port(const uint8_t pin) {
19
  return (pin<8) ? IODIRA : IODIRB;
20
}
21

22
constexpr uint8_t get_OUT_Port(const uint8_t pin) {
23
  return (pin<8) ? OLATA : OLATB;
24
}
25

26

27
// >>> Output <<< ------------------------------------------------------------------
28
template <uint8_t i2cAdr, uint8_t pin>
29
class Output
30
{   
31
  public:  
32
  // Konstruktor    
33
  Output ()
34
  { }
35

36
  void init()
37
  {   
38
    setLow();                        // verhindert Einschaltzucken
39
    uint8_t is = 0;    
40
    readOneRegister (i2cAdr, get_DIR_Port(pin), is);    // IODIR Port lesen
41
    is &= ~(getBitMaske(pin));               // Bit löschen
42
    writeOneRegister (i2cAdr, get_DIR_Port(pin), is);    // modifizierte Maske zurückschreiben
43
  } 
44

45
  void setHigh()
46
  {   
47
    uint8_t is = 0;
48
    readOneRegister (i2cAdr, get_OUT_Port(pin), is);    // OLAT Port lesen
49
    is |= (getBitMaske(pin));               // Bit setzen
50
    writeOneRegister (i2cAdr, get_OUT_Port(pin), is);    // modifizierte Maske zurückschreiben
51
  } 
52

53
  void setLow()
54
  {   
55
    uint8_t is = 0;
56
    readOneRegister (i2cAdr, get_OUT_Port(pin), is);    // OLAT Port lesen
57
    is &= ~(getBitMaske(pin));               // Bit löschen
58
    writeOneRegister (i2cAdr, get_OUT_Port(pin), is);    // modifizierte Maske zurückschreiben
59
  } 
60

61
  void set(const bool state)                            // für Zustandsentscheidung während der Laufzeit
62
  {   
63
    if (state)  setHigh();
64
    else  setLow();
65
  } 
66
};

Ist kostenlos, ich hoffe das es nicht umsonst ist.  :-)
Gast #6135211
Lesenswert?

Das hilft mir schon einwenig aber sehe ich das demfalle Richtig das ich 
für jeden einzelenes commando einen Restart machen muss und neu 
beginnen.

also CS /Write,0x40,0x14 CS/restart
     CS /write,0x40,0x00,0x04
somit müsste Pin3 PORTA = 3,3V sein
Gast #6135281
Lesenswert?

Ja ich meine eher du must die Richtung im Register festlegen. Also musst 
du sicher mal zu dem Register navigieren. und mit restart meine ich CS 
High und dan erneut Low und neu beginnen. Oder verstehe ich das falsch.
Gast #6135290
Lesenswert?

Mein Jetziger code ist bis jetzt so


CSlow;
SPIT(0x40);//Schreiben
SPIT(0x14);//IODIRA
SPIT(0x00);//Richtung Ausgang
CShigh;

CSlow;
SPIT(0x40);//Schreiben
SPIT(0x14);//OLATA
SPIT(0x04);//Output
CShigh;
#6135292
Lesenswert?

Ich hatte bei 23S17 noch das Problem, dass die drei Adressleitungen - 
auch im SPI-Modus - auf 0 gesetzt werden mussten, sonst war der nicht 
ansprechbar. Die Adresse wird beim 23S18 ja analog über 
Widerstandsteiler eingestellt. Wenn es nicht geht, vielleicht mal hier 
den Pin so beschalten, dass Adresse 0 eingestellt ist. Hab ich zwar beim 
Überfliegen des Datenblatts nicht gesehen, dass das erforderlich ist, 
aber der Teufel steckt ja manchmal im Detail.

Viele Grüße

Jochen
Gast #6135297
Lesenswert?

Muss korrigieren
Aber leider klappt es so nicht ev können noch SPI Einstellungen wie CPOL 
oder CPHA falsch sein sind jetzt CPOL 1 und CPHA 1

CSlow;
SPIT(0x40);//Schreiben
SPIT(0x00);//IODIRA
SPIT(0x00);//Richtung Ausgang
CShigh;

CSlow;
SPIT(0x40);//Schreiben
SPIT(0x14);//OLATA
SPIT(0x04);//Output
CShigh;
#6135302
Lesenswert?

Ne, ich sehe gerade, du schreibst zweimal in die 0x14 (OLATA). Du musst 
einmal in 0x00 schreiben (IODIRA), am besten 0x00, dann sind alle Pins 
Ausgänge. Und dann in 0x12 (GPIOA), was ausgegeben werden soll. 0x14 
(OLATA) sollte auch klappen - Schreibzugriffe in die 0x12 werden an 0x14 
durchgereicht.

Viele Grüße

Jochen
Gast #6135307
Lesenswert?

Jochen S. schrieb:
> Wenn du kein Oszi zur Hand hast, musst du die vier Modi, die
> möglich
> sind, ausprobieren. Sonst einfach mal schauen, ob die Signalform so ist,
> wie sie laut Datenblatt sein muss.
>
> Viele Grüße
>
> Jochen

Ja die Signalform stimmt bei steigender Flanke wird eingelesen von dem 
Clock und die Zeitabstände stimmen auch wie sie sollten.
#6135313
Lesenswert?

Kannst ja auch das Lesen mal probieren. Zwei Register sind beim 
Anschalten mit 0xFF, alle anderen mit 0x00 initialisiert. Wenn du die 
ganze Register-Map ab Adresse 0x00 ausliest, siehst du ja, ob diese 
Antworten dabei sind oder alles 0xFF ist, was darauf deuten lässt, dass 
sich der Chip nicht angesprochen fühlt. Dann wie gesagt mal den ADDR-Pin 
so beschalten, dass Adresse 0 eingestellt ist.

Viele Grüße

Jochen
Beitrag #6135314 wurde von einem Moderator gelöscht.
Gast #6135358
Lesenswert?

Mit einem 23S17 bin ich auf die Nase gefallen wegen Spannungen.

µC BluePill mit 3V3
23S17 an 5V und an den GPIOs ein 5V LCD128x64 uralt.

Ging ein paar Stunden/Tage und dann war Schluß, LCD reagierte nicht 
mehr.

Ursache ist der geringe High-Level an SPI wenn der 23S13 an 5V hängt.
Der mag da Vcc*0.8 mindestens als high sehen

Simple Diode reduziert die ChipSpannung und schon paßt es.

Läuft alles an gleicher Spannung sollte das keine Thema sein.
Gast #6146972
Lesenswert?

> Ging ein paar Stunden/Tage und dann war Schluß, LCD reagierte nicht
> mehr.
>
> Ursache ist der geringe High-Level an SPI wenn der 23S13 an 5V hängt.
> Der mag da Vcc*0.8 mindestens als high sehen

Also das hab ich jetz auch noch überprüft. Die Spannung geht wirklich 
auf 0.0-0.1V runter also kaum das dies noch als high gesehen werden 
könnte.
#7484247
Lesenswert?

Hallo Horst,

ich habe eine Frage zum IOCON-Register. Alle anderen Register gibt es einmal für A und B. Daher dachte ich, dass es das IOCON Register nur einmal gibt. Auf der Register-Karte taucht IOCON aber einmal bei A und einmal bei B auf. Allerdings werden da ja auch Parameter eingetragen, die beide Register betreffen?

Ich sag' im Voraus schonmal besten Danke!

MfG Manuel

#7484338
Lesenswert?

Also wer sich das ausgedacht hat, das Register zur Bankumschaltung auf unterschiedliche Adressen je Bank zu setzen, der hat nicht alle Tassen im Schrank.

Um aus einem unbekannten BANK-Bit heraus zu kommen, muß man auf Verdacht 2 Zugriffe machen, auf 05 und auf 0B. Steht das Bit falschrum, schreibt man entweder auf ein nicht vorhandenes Register oder auf GPINTENB, was zumindest keinen Portpin falsch setzt.

#7484355
Lesenswert?

Ja, das ist nicht durchdacht, da gebe ich dir recht. Aber im Normalfall entscheidet man sich ja beim Initialisieren für das eine oder das andere Modell und setzt das Bit entsprechend. Ein Problem ist es nur, wenn der Chip einmal initialisiert wurde und dann der Mikrocontroller z. B. durch Watchdog Reset neu startet. Dann weiß er halt nicht, dass der 23S18 noch im anderen Bankmodell ist und macht Blödsinn. Als Workaround kann man beim Initialisieren natürlich alle in Frage kommenden Register auf 0 zurücksetzen, was dem Zustand nach Reset entspricht, und dann die eigentliche Initialisierung zu machen. Umständlich, aber kein unlösbares Problem.

Viele Grüße

Jochen

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren