Tastendruckabfrage in der ISR an einem PCF8574

Hallo an alle!

Ich brauche dringend eure Hilfe und Tipps!!!

Ich möchte gerne eine 3x4 Matrix Tastatur über den Tastatur-Controller
PCF8574 ansteuern.

Nach jedem Tastendruck soll ein Interrupt ausgeführt werden, der dann in der Interrupt Service Routine abgefragt wird und auf dem Seriellen Monitor ausgegeben wird (später auf dem LCD).

Zur Zeit wird die Tastatur nur in der loop schleife abgefagt, was auch wunderbar funktioniert.

Hier der Code:

#include <Wire.h>
#include <i2ckeypad.h>

#define ROWS 4
#define COLS 3

#define PCF8574_ADDR 0x20

i2ckeypad kpd = i2ckeypad(PCF8574_ADDR, ROWS, COLS);

int interrupt0 = 0;  
int i = 0;
volatile char taste = '\0';

void setup()
{
  Serial.begin(9600);

  Wire.begin();

  kpd.init();
  
  attachInterrupt(interrupt0, interruptroutine, FALLING); 
  
}

void loop()
{
  
  taste = kpd.get_key();

  if(taste != '\0') {
        Serial.print(taste);
        Serial.print("\n");
        Serial.print("interrupt=");
        Serial.print(i);
        Serial.print("\n");
  }
  
}
 void interruptroutine() //InterruptKeyCode
 {  
    i++;
 }  
  

Code ende


hier die i2ckeypad.cpp Datei:

#include "i2ckeypad.h"
#include <Wire.h>

// Arduino Versionierung.
#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

/*
 *  PIN MAPPING
 *
 *  Hier können Sie Ihre Wire Zuordnung zwischen Ihrer Tastatur und PCF8574 ändern
 *  Standard-Mapping für Sparkfun 4x3 Tastatur
 */

#define COL0  2  // P2 PCF8574 ist COL0 gewöhnlich Pin 3 3x4 Tastaturen
#define COL1  0  // P0 of PCF8574, col1 is usually pin 1 of 4x3 keypads
#define COL2  4  // P4 of PCF8574, col2 is usually pin 5 of 4x3 keypads

#define ROW0  1  // P1 of PCF8574, row0 is usually pin 2 of 4x3 keypads
#define ROW1  6  // P6 of PCF8574, row1 is usually pin 7 of 4x3 keypads
#define ROW2  5  // P5 of PCF8574, row2 is usually pin 6 of 4x3 keypads
#define ROW3  3  // P3 of PCF8574, row3 is usually pin 4 of 4x3 keypads


/*
 * Taste auf dem Tastenfeld MAPPING
 *
 */

const char keymap[4][5] =
{
  "789",
  "456",
  "123",
  "0SE"
};


/*
 *  VAR und Konstanten DEFINITION. 
 */

// Standardeinstellung Zeile und Spalte Pinzahlen
int num_rows = 4;
int num_cols = 3;

// PCF8574 i2c Addresse
int pcf8574_i2c_addr;

// Aktuelle Reihe suchen
static int row_select;

// Aktuelle Daten des PCF8574 festgelegt
static int current_data;

//Hex-Byte-Anweisung für jeden Port des PCF8574
const int hex_data[8] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};

// Hex-Daten für jede Zeile der Tastatur vom PCF8574
const int pcf8574_row_data[4] = 
{
  hex_data[ROW1] | hex_data[ROW2] | hex_data[ROW3] |
  hex_data[COL0] | hex_data[COL1] | hex_data[COL2],
  hex_data[ROW0] | hex_data[ROW2] | hex_data[ROW3] |
  hex_data[COL0] | hex_data[COL1] | hex_data[COL2],
  hex_data[ROW0] | hex_data[ROW1] | hex_data[ROW3] |
  hex_data[COL0] | hex_data[COL1] | hex_data[COL2],
  hex_data[ROW0] | hex_data[ROW1] | hex_data[ROW2] |
  hex_data[COL0] | hex_data[COL1] | hex_data[COL2],
};

// Hex-Daten für jeden Spalte der Tastatur vom PCF8574
int col[3] = {hex_data[COL0], hex_data[COL1], hex_data[COL2]};


/*
 *  CONSTRUCTORS
 */

i2ckeypad::i2ckeypad(int addr)
{
  pcf8574_i2c_addr = addr;
}

i2ckeypad::i2ckeypad(int addr, int r, int c)
{
  pcf8574_i2c_addr = addr;
  num_rows = r;
  num_cols = c;
}


/*
 *  PUBLIC METHODS
 */

void i2ckeypad::init()
{
  // Alle PCF8574 Ports auf HIGH setzen
  pcf8574_write(pcf8574_i2c_addr, 0x15);

  // Beginnen mit der ersten Zeile der Matrix-Tastatur
  row_select = 0;
}

char i2ckeypad::get_key()
{
static int temp_key;

  int tmp_data;
  int r;

  int key = '\0';
  
 while( temp_key != keymap[row_select][r])
{
  

  // Suchen Zeile LOW
  pcf8574_write(pcf8574_i2c_addr, pcf8574_row_data[row_select]);

  for(r=0;r<num_cols;r++) {
    // Lesen PCF8574 Port-Daten
    tmp_data = pcf8574_byte_read(pcf8574_i2c_addr);

    // UM XOR zu erhalten und Aktuelle Daten zu vergleichen und um zu erkennen, 
    // wenn eine Spalte LOW ist
    tmp_data ^= current_data; // current_data = Aktuelle daten

    // Taste gedrückt!
    if(col[r] == tmp_data) {
      temp_key = keymap[row_select][r];
      return '\0';
    }
  }

  // Taste wurde gedrückt und dann freigegeben
  if((key == '\0') && (temp_key != '\0'))    
  {
    key = temp_key;
    temp_key = '\0';
    return key;
  }

  // Alle PCF8574 Ports wieder auf HIGH schalten
  pcf8574_write(pcf8574_i2c_addr, 0x15);

  // Nexte Zeile
  row_select++; // inkrementiere row_select
  if(row_select == num_rows) { // Wenn du bei der letzten Zeile angekommen bist, 
    row_select = 0;			   // dann beginnne wieder bei der ersten Zeile
  }

  return key;
 } 

}

/*
 *  PRIVATE METHODS
 */

void i2ckeypad::pcf8574_write(int addr, int data)
{
  current_data = data;

  Wire.beginTransmission(addr); //Beginnt eine Übertragung als Master an die in address angegebene
								//Slave-Adresse.
								
  Wire.write(data);				//Speichert Daten in einer Warteschleife, die dann an eine Slave-
								//Adresse gesendet werden. Wire.write() wird nach wire.beginTransmission
								//aufgerufen. 
								
  Wire.endTransmission();		//Beendet den Übertragungsblock
}

int i2ckeypad::pcf8574_byte_read(int addr)
{
  Wire.requestFrom(addr, 1);	//Fordert von einem Gerät Informationen an. Adresse bezeichnet
								//dabei die 7-Bit-Geräteadresse, quantity die Anzahl von Bytes, 
								//die erwartet werden.

  return Wire.read();			//Empfängt das nächste Byte, das auf dem Bus für das Gerät vorliegt
								//und gibt dieses als »Byte« zurück.
}

Vielen Dank im Voraus
Ich hoffe mir kann jemand helfen

aus dem Datenblatt http://www.nxp.com/documents/data_sheet/PCF8574.pdf :

7.2 Interrupt output
The PCF8574 provides an open-drain output (INT) which can be fed to a corresponding input of the microcontroller (see Figs 13 and 14). This gives these chips a type of master function which can initiate an action elsewhere in the system.
An interrupt is generated by any rising or falling edge of the port inputs in the input mode. After time tiv the signal INT is valid.
Resetting and reactivating the interrupt circuit is achieved when data on the port is changed to the original setting or data is read from or written to the port which has generated the interrupt.
Resetting occurs as follows:
• In the READ mode at the acknowledge bit after the rising edge of the SCL signal
• In the WRITE mode at the acknowledge bit after the HIGH-to-LOW transition of the SCL signal
• Interrupts which occur during the acknowledge clock pulse may be lost (or very short) due to the resetting of the interrupt during this pulse.
Each change of the I/Os after resetting will be detected and, after the next rising clock edge, will be transmitted as INT. Reading from or writing to another device does not
affect the interrupt circuit.

Wenn ich mich nicht irre, sind die ROW-Pins Ausgänge beim PCF und die COL-Pins Eingänge (ansonsten ist das gesagte umgekehrt)
Damit Du eine x-belibige Tastenbetätigung der Matrixtastatur erkennen kannst, mußt Du auf den COLs Pullup- (oder Pulldown) -widerstände schalten. Dann ALLE ROW-Ausgänge auf LOW (bzw HIGH) schalten. Der /INT Ausgang des PCF auf den Interrupteingang des Arduino mit Pullupwiderstand schalten. Interrupt auf falling triggern und in der Interruptroutine die gesamte Tastaturabfrage der Matrix machen.

Grüße Uwe

Hallo Uwe!

Danke erst einmal für die schnelle Antwort!

Wenn ich die Tastaturabfrage in der Interruptroutine mache, hängt sich der µC auf.

Kann das sein, dass die Abfrage zu lange dauert.

Sollen Interrupt Service Routine nicht so schnell wie möglich beendet werden oder muss ich noch Flag setzen?

Danke!

[
#include <Wire.h>
#include <i2ckeypad.h>

#define ROWS 4
#define COLS 3

#define I2C_Adresse 0x20

i2ckeypad kpd = i2ckeypad(I2C_Adresse, ROWS, COLS);

int interrupt0 = 0;  // Interrupt-Nummer (es gibt 2 Interrupts INT 0(PIN2) und INT 1(PIN3))
int i = 0;
volatile int taste = '\0';

void setup()
{
  Serial.begin(9600);

  Wire.begin();

  pinMode(PIN2, INPUT);
  digitalWrite(PIN2, HIGH);
  
  kpd.init();
  
  attachInterrupt(interrupt0, interruptroutine, FALLING); //Der Interrupt-Modus steht jetzt auf RISING, was bedeutet,
                                                        // dass nur auf die ansteigende Flanke des Tasters reagiert wird.
  Serial.print("Testing keypad/PCF8574 I2C port expander arduino \n\n");
}

void loop()
{
  
  //taste = kpd.get_key();

   if(taste != '\0')
   {
      Serial.print(taste);
      Serial.print("\n");
      Serial.print("interrupt=");
      Serial.print(i);
      Serial.print("\n");
        
   }
  
}
 void interruptroutine() //InterruptKeyCode
 {  
    i++;
    taste = kpd.get_key();
 }  
  
    ]

Du darfst wahrscheinlich die Bibliothek nicht in der Interruptroutine verwenden sondern die Tastaturabfrage selbst machen.
Die Interruptroutine wird wahrscheinlich nie angesprungen weil die Tastatur nicht so initialisiert ist daß am PCF jeder Tastendruck einen Interrupt aufruft. Du hast eigentlcih gar nichts gemacht, was ich Dir vorgeschlagen habe.
Grüße Uwe

Hallo Uwe!

Sorry, wenn ich dich falsch verstanden habe.
Ich dachte, ich hatte alles umgesetzt.

An den COL-Pins (Eingängen), habe ich die Pullup-widerstände (10kOhm) geschaltet und die ROW-Ausgänge liegen an LOW Ausgang. Am INT0 habe ich den internen Pullupwiderstand freigeschaltet. Interrupt auf falling getriggert( attachInterrupt(interrupt0, interruptroutine, FALLING):wink: Jetzt muss ich nur noch in der ISR die Matrix abfragen.

Darf man innerhalb von ISR Funktionen aufrufen?

Der Interrupt wird ausgeführt, das konnte ich am oszilloskop sehen.

Lg Sven

Hallo an Alle!!

Hallo Uwe!

Bin schon am verzweifeln, ich bekomme es einfach nicht hin.

Ich habe den Code, jetzt so geschrieben, dass die Abfrage in der Interruptroutine stattfindet.
Der µC hängt sich wieder auf. Ich vermute es liegt an der Wire Bibliothek.

Gibt es nicht irgendeine andere möglichkeit?
Kann man nicht innerhalb der Interruptroutine ein Flag setzen und ausserhalb abfragen?

Gruß Sven

[

#define I2C_Adresse 0x20


#include <Wire.h>

byte tmp_data;
int i = 3;
byte current_data;
byte read_data;
volatile char Key = '\0';
int row_select= 0;
int c;

int interrupt0 = 0;  // Interrupt-Nummer (es gibt 2 Interrupts INT 0(PIN2) und INT 1(PIN3))

// Daten für jede Zeile der Tastatur vom PCF8574
const int pcf8574_row_data[4] = 
{
  B01101111, B01011111, B00111111 
};
const char KeyCode[4][5] =
{
  "026",
  "7S3",
  "48E",
  "159"
};

void setup()
{
  Serial.begin(9600);

  Wire.begin();
  
  pinMode(PIN2, INPUT);
  digitalWrite(PIN2, HIGH);
  
  i2c_write(0x0F);
  
  attachInterrupt(interrupt0, interruptroutine, FALLING); //Der Interrupt-Modus steht jetzt auf RISING, was bedeutet,
                                                        // dass nur auf die ansteigende Flanke des Tasters reagiert wird.
  Serial.print("Testing keypad/PCF8574 I2C port expander arduino lib\n\n");
}

void loop()
{
     
     
    if (Key != '\0')
     {
     
     Serial.print("\n Key = ");
     Serial.print(Key);
     
    Key= '\0';
     
     }
  
}
 void interruptroutine() //InterruptKeyCode
 { 
   
for(c=0;c<3;c++)
{
    // Suche Zeile LOW
    i2c_write(pcf8574_row_data[i]); 
    
    // Lesen PCF8574 Port-Daten
    read_data = i2c_read();     // receive a byte as character
      
    // UM XOR zu erhalten und um zu erkennen ob eine Spalte LOW ist
    tmp_data = read_data ^ current_data; // current_data = Aktuelle daten
    
  
    
    tmp_data = tmp_data >>i;
  
     if (tmp_data == 1)
     {
      Key = KeyCode[row_select][c];
      
      
      tmp_data = '0/';
     }
  
   
  i--;
     if(i < 0) 
  {  
    i = 3;
  }
   
   }
   
  
   //Nächste Zeile
      row_select++;
     if(row_select == 4) 
  { 
                       // Wenn du bei der letzten Zeile angekommen bist
    row_select = 0;    // dann beginnne wieder bei der ersten Zeile
  } 
   			   
 }  
  

 void i2c_write(int data)
{
  current_data = data;

  Wire.beginTransmission(I2C_Adresse); //Beginnt eine Übertragung als Master an die in address angegebene
				       //Slave-Adresse.								
  Wire.write(data);		       //Speichert Daten in einer Warteschleife, die dann an eine Slave-
				       //Adresse gesendet werden. Wire.write() wird nach wire.beginTransmission								//aufgerufen.								
  Wire.endTransmission();	       //Beendet den Übertragungsblock
}

int i2c_read()
{
  Wire.requestFrom(I2C_Adresse, 1);	//Fordert von einem Gerät Informationen an. address bezeichnet
					//dabei die 7-Bit-Geräteadresse, quantity die Anzahl von Bytes, 								//die erwartet werden.
  return Wire.read();			//Empfängt das nächste Byte, das auf dem Bus für das Gerät vorliegt
					//und gibt dieses als »Byte« zurück.
}
   
]

Hast Du Deinen Code mal ohne Interrupts getestet? Gibt der Chip über I2C überhaupt Antwort? Was ist der Rückgabewert von Wire.endTransmission()?

Hallo pylon!

Ja, den Code habe ich ohne Interrupt getestet. Ohne Interrupt kann ich jede Taste abfragen, solbald ich es mit dem Interrupt probiere hägt sich der µC.

Hier der Code ohne Interrupt:

#include <Wire.h>

#define I2C_Adresse 0x20

byte tmp_data;
int i =3;
byte current_data;
byte read_data;
static char Key = '\0';
int row_select= 0;
int c;
int v;


// Daten für jede Zeile der Tastatur vom PCF8574
const int pcf8574_row_data[4] = 
{
  B01101111, B01011111, B00111111 
};
const char KeyCode[4][5] =
{
  "026",
  "7S3",
  "48E",
  "159"
};

void setup()
{
  Wire.begin();        )
  Serial.begin(9600);  // start serial for output
  
   pinMode(PIN2, INPUT);
  digitalWrite(PIN2, HIGH);
  
}

void loop()
{
 
  
  if (Key != '\0')
     {
     
     Serial.print("\n Key = ");
     Serial.print(Key);
    
    Key= '\0';
     
     }
  
 

  
for(c=0;c<3;c++)
{
  
  				
  i2c_write(pcf8574_row_data[c]);
  
  
     
  
  read_data = i2c_read();    // receive a byte as character
    
    // UM XOR zu erhalten und die aktuellen Daten zu vergleichen und um zu erkennen, 
    // wenn eine Spalte LOW ist
    
  tmp_data = read_data ^ current_data; // current_data = Aktuelle daten
    
    
    tmp_data = tmp_data >>i;
  
     if (tmp_data == 1)
     {
      Key = KeyCode[row_select][c];
      
      delay(500);
      tmp_data = '0/';
     }
    
  i--;
     if(i < 0) 
  {  
    i = 3;
  }
   
   }
       //Nächste Zeile
      row_select++;
     if(row_select == 4) 
  {                               // Wenn du bei der letzten Zeile angekommen bist, 
    row_select = 0;	     // beginnne wieder bei der ersten Zeile
  }
 
}


 void i2c_write(int data)
{
  current_data = data;

  Wire.beginTransmission(I2C_Adresse);  //Beginnt eine Übertragung vom Master zum Slave.							
  Wire.write(data);			//Speichert die Daten in einer Warteschleife, die dann an eine Slave-
				//Adresse gesendet wird. Wire.write								
  Wire.endTransmission();		//Beendet den Übertragungsblock
}

int i2c_read()
{
  Wire.requestFrom(I2C_Adresse, 1);	//Fordert von einem Gerät Informationen an. 
				//dabei die 7-Bit-Geräteadresse, quantity die Anzahl von Bytes, 								              //die erwartet werden.
  return Wire.read();		//Empfängt das nächste Byte, das auf dem Bus für das Gerät vorliegt
				//und gibt dieses als »Byte« zurück.
}

Hallo!

Kann mir denn niemand helfen? :frowning:

Gruß Sven

TerraHoff:
Kann mir denn niemand helfen? :frowning:

Was Du da vorhast, mit einem per Portexpander angeschlossenen Keypad innerhalb einer Interruptbehandlung per I2C-Kommunikation mit einer nicht interruptfesten fremden Library die Tasten abzufragen, halte ich für ein aussichtsloses Unterfangen.

Mein Vorschlag:
Entweder schließt Du Dein Keypad ohne Portexpander direkt an 7 I/O Pins des Arduino an und wertest den Zustand innerhalb einer Interruptbehandlung selbst aus, falls Du genügend freie I/O Pins hast. Oder Du verabschiedest Dich von der Idee, die Tastaturabfrage über Portexpander und I2C-Kommunikation unbedingt innerhalb einer Interruptbehandlung machen zu wollen und machst die Abfrage in der loop-Funktion.

Wenn Dein Programmdesign nicht komplett verhunzt ist, läuft die loop-Funktion doch selbst in einem komplexen Programm (das natürlich frei von "delay()" sein muss) mindestens auf einer "Drehzahl" von 1000 pro Sekunde, d.h. Du kannst innerhalb von einer Millisekunde in der loop() auf einen Tastendruck reagieren, und das ist um zwei Zehnerpotenzen schneller als die normale menschliche Reaktionszeit.

Hallo jurs!

Danke für deine Hilfe. :slight_smile:

Ich werde mich dann vom Port Expander PCF8574 verabschieden und es direkt probieren.

Gruß Sven