PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : PCF8574 nach Interrupt an INT2 auslesen



Reissdorf
21.03.2013, 12:11
Hallo zusammen,

um ehrlich zu sein, verzweifel ich an meiner PCF8574-Schaltung. Die folgende Funktion sollte hier stattfinden:



Ein, am PCF8574 angeschlossener, Schalter wird gedrückt und zieht den entsprechenden Pin auf GND
Der PCF8574 löst daraufhin ein Interrupt aus und zieht das INT-Signal auf GND
Der Mikrocontroller erkennt das INT-Signal und springt zur entsprechende ISR
In der ISR wird der PCF ausgelesen und das Ergebnis für eine LCD Anzeige umgewandelt
Das LCD Modul sollte nun die ausgelesenen 8 Bit darstellen


Die LCD-Anzeige wird auch über I2C angesprochen, sie liefert aber kein INT-Signal zurück (das kann also keine Störung verursachen)!

Ich schaffe es, dass der Interrupt ein Auslesen anstößt und etwas, dass wie Bits aussieht, ausgibt. Drücke ich Taster 2, der am P6 des PCF8574 hängt, dann sollte ein "10111111" angezeigt werden. Das habe ich jetzt dreimal getestet und einmal habe ich ein "11011001", dann ein "00111111" und schließlich ein "11111111" erhalten. Nach dem letzten Ergebnis tillte die Anzeige völlig aus und zeigte, in rasender Folge, wirre Zeichen auf dem LCD-Display.

Hier der Code und die PCF8574 Schaltung und darunter der Schaltplan. Ich hoffe sehr, dass mir irgendjemand weiterhelfen kann, denn mittlerweile habe ich den Durchblick völlig verloren :(


#ifndef F_CPU //Wenn CPU-Takt nicht bereits definiert wurde...
#define F_CPU 16000000 //...dann definiere ihn auf 16MHz
#endif


#include "i2clcd.h"
#include "i2cmaster.h"
#include <avr/io.h>
#include <avr/interrupt.h>


volatile unsigned char adr1_w = 0x42; // Device 1 write-address
volatile unsigned char adr1_r = 0x43; // Device 1 read-address
unsigned char read_data; // Variable für Leseergebnis
volatile unsigned char s[2]; // Var. für Bitdarstellung von 'read_data'


void init_INT2(void) {
GICR &= ~(1<<INT2); // INT2 deaktivieren
MCUCSR &= ~(1<<ISC2); // Bei fallender Flanke Interrupt aktivieren
GIFR |= (1 << INTF2); // Interrupt Flag setzen (s.S 67 ATMEGA32 Datasheet)
GICR |= (1 << INT2); // INT2 aktivieren
}
void init_prog(void) {
cli(); // Interrupts deaktiviert
i2c_init(); // Starte I2C Bus
lcd_init(); // Starte I2CLCD
lcd_command(LCD_CLEAR); // Leere Display
lcd_wait_ms(30); // Warte 3ms
}
void init_IO(void){
DDRB &= ~(1 << DDB2); // PB2 = Eingang für Interrupt
PORTB |= (1 << PB2); // internen Pull-Up an PB2 aktivieren
DDRD |= (1 << DDD7); // PD7 = Ausgang => LED 4
PORTD |= (1 << PD7); // PD7 = High => LED4 an
}


int main(void) {
cli(); // Interrupts deaktiviert
init_IO();
init_INT2();
init_prog();
sei(); // Interrupts aktiviert
while(1){
lcd_printlc(2,1,(unsigned char *)s);
}}


ISR(INT2_vect){
PORTD &= ~(1<<PD7); // PD7 = Low => LED4 aus
read_data = 0; // read_data zurücksetzen
i2c_start(adr1_r); // Starte Lesezugriff
read_data = i2c_readNak(); // read_data mit Leseergebnis beschreiben
itoa(read_data,s,2); // read_data als ASCII in s schreiben
}

24878

Liquidator
21.03.2013, 13:54
Tastenprellung und Lötfehler entfallen völlig?

MfG Nik

Reissdorf
21.03.2013, 14:26
Hallo Nik,

Lötfehler mag ich tatsächlich ausschließen wollen. Wenn ich PB2 (INT2) als normalen Eingang beschalte, dann funktioniert der einwandfrei. Wenn in diesem Fall das Low-Signal vom Interrupt kommt, habe ich in der ISR die LED an PD7 abgeschaltet (die Abschaltanweisung ist sogar noch in der o.g. ISR drin). Klappt auch wie geplant. Ich glaube daher, dass das richtig verlötet ist.
Was die Tasterprellung angeht, hast du natürlich recht. Es könnte sein, dass der Interrupt häufiger kurz hintereinander ansteht. Aber damit rufe ich doch nur häufiger und kurz hintereinander die ISR auf, oder nicht? Warum sollte er mir falsche Werte anzeigen oder extrem viele wirre Zeichen, die auf dem Display zu scrollen scheinen? Was meinst du dazu?

Gruß, Reissdorf

robocat
21.03.2013, 16:32
Auf jeden Fall sind die 2 chars, in die du mit itoa deinen Binärstring reinschreiben willst, zu klein. 9 Bytes wirst du da schon spendieren müssen, und das 9te muss die abschliessende 0 sein, wenn deine print Routine nullterminierte Strings verlangt.

Grüße von der Katze

Reissdorf
21.03.2013, 17:52
Hallo nochmal,

ich hab mich heute nochmal sehr intensiv mit diesem Problem beschäftigt und auch nochmal andere Foreneinträge dazu gelesen. Dabei bin ich auf interessante Gedanken gebracht worden. Dabei ist der folgende Code rausgekommen:


#ifndef F_CPU //Wenn CPU-Takt nicht bereits definiert wurde... #define F_CPU 16000000 //...dann definiere ihn auf 16MHz
#endif


#include "i2clcd.h"
#include "i2cmaster.h"
#include <avr/io.h>
#include <avr/interrupt.h>


volatile unsigned char adr1_w = 0x42; // Device 1 write-address
volatile unsigned char adr1_r = 0x43; // Device 1 read-address
unsigned char read_data; // Variable für Leseergebnis
volatile unsigned char s[16]; // Var. für Bitdarstellung von 'read_data'


void init_INT2(void) {
GICR &= ~(1<<INT2); // INT2 deaktivieren
MCUCSR &= ~(1<<ISC2); // Bei fallender Flanke Interrupt aktivieren
GIFR |= (1 << INTF2); // Interrupt Flag setzen (s.S 67 ATMEGA32 Datasheet)
GICR |= (1 << INT2); // INT2 aktivieren
}
void init_prog(void) {
cli(); // Interrupts deaktiviert
i2c_init(); // Starte I2C Bus
lcd_init(); // Starte I2CLCD
lcd_command(LCD_CLEAR); // Leere Display
lcd_wait_ms(30); // Warte 3ms
}
void init_IO(void){
DDRB &= ~(1 << DDB2); // PB2 = Eingang für Interrupt
PORTB |= (1 << PB2); // internen Pull-Up an PB2 aktivieren
DDRD |= (1 << DDD7); // PD7 = Ausgang => LED 4
//LED 4 ist die Interrupt-Kontrollleuchte
PORTD &= ~(1<<PD7); // PD7 = Low => LED4 aus
}


int main(void) {
cli(); // Interrupts deaktiviert
init_IO(); // Ein-/Ausgänge initiieren
init_INT2(); // Interrupt2 einschalten
init_prog(); // I2C und I2CLCD aktivieren
read_data = 0xff; // Grundwert für 'read_data'
sei(); // Interrupts aktiviert
while(1){
sei();
if (read_data == 0xff){
lcd_printlc(1,1,"Keine Taste ");
}
if (read_data == 0x7f){
lcd_printlc(1,1,"S1 gedrückt ");
}
if (read_data == 0xdf){
lcd_printlc(1,1,"S3 gedrueckt ");
}
}
}


ISR(INT2_vect){
cli();
PORTD |= (1 << PD7); // PD7 = High => LED4 an
uint8_t tmp_sreg; // temporaerer Speicher fuer das Statusregister
tmp_sreg = SREG; // Statusregister (also auch das I-Flag darin) sichern
lcd_wait_ms(10); // Warte 10ms
i2c_start(adr1_r); // Starte Lesezugriff
read_data = i2c_readNak(); // read_data mit Leseergebnis beschreiben
SREG = tmp_sreg; // Status-Register wieder herstellen
PORTD &= ~(1<<PD7); // PD7 = Low => LED4 aus
}



Die ISR schaltet kurz die LED4 an, womit ich sehen kann, dass die ISR auch aktiviert wurde. Am Ende der ISR wird sie wieder ausgeschaltet. Das kurze Flackern reicht völlig.
Sicherheitshalber deaktiviere ich dann die Interruptfunktion (Cli();) und speicher das Statusregister (hab ich so aus dem Internet).
Mit einer Warteschleife von 10ms passe ich auch noch Tastenpreller ab. Das ist nicht sauber programmiert, da in einer Interrupt-Routine keine Wartefunktion benutzen werden sollte. Aber es geht mir erstmal nur darum, dass die ISR funktioniert.
Nun wird gelesen und das Ergebnis in read_data geschrieben
Dann noch Status-Register wiederherstellen und LED4 aus und schon passt es.


In der while-Schleife schaue ich nur noch welchen Wert read_data hat und gebe einen entsprechenden Text auf dem LCD-Display aus. Funktioniert super!

Jetzt schau ich noch, wie die Entprellung besser gemacht werden kann und baue eine Bitmanipulation ein um die LEDs am PCF8574 ein- und ausgeschaltet zu lassen bei zukünftigen Schreibprozeduren.

Bis hierher erstmal vielen lieben Dank für die Anregungen!!
Reissdorf :)

- - - Aktualisiert - - -

Hallo Robocat,

den Einwand mit den 2 Chars verstehe ich nicht so ganz. Die Kombination aus "volatile unsigned char s[2];" und "itoa(read_data,s,2);" sagt doch nur aus, dass read_data als Bitdarstellung angezeigt werden soll. s[10] und "itoa(read_data,s,10);" wäre eine dezimale, s[16] und "itoa(read_data,s,16);" eine hexadezimale Anzeige. Zumindest habe ich das bislang immer so gemacht. Ist das falsch?

LG, Reissdorf

Liquidator
21.03.2013, 19:41
Wenn es der Platz zulässt, würde ich das "Glätten" der Prellung hardwaretechnisch lösen. (http://www.mikrocontroller.net/articles/Entprellung)
Aber das weißt du sicherlich schon - viel Erfolg :)

MfG Nik

robocat
21.03.2013, 20:37
Die Kombination aus "volatile unsigned char s[2];" und "itoa(read_data,s,2);" sagt doch nur aus, dass read_data als Bitdarstellung angezeigt werden soll. s[10] und "itoa(read_data,s,10);" wäre eine dezimale, s[16] und "itoa(read_data,s,16);" eine hexadezimale Anzeige. Zumindest habe ich das bislang immer so gemacht. Ist das falsch?

Ja, das ist falsch. Die Darstellung eines Bytes als Dezimalzahl benötigt 3+1 chars (0-255 und abschliessende \0), Hexadezimal 2+1 (0-FF) und Binär 8+1 chars. 11110000 sind nun mal 8 Zeichen, wenn du weniger reserviert hast kann es gutgehen (das sind oft schwer zu findende Fehler, die nur sporadisch auftreten), es werden aber nachfolgende andere Variablen zur Laufzeit überschrieben. Du bräuchtest hier ein s[9].

http://www.cplusplus.com/reference/cstdlib/itoa/

Grüße von der Katze

Reissdorf
22.03.2013, 09:38
Ach so! Jetzt versteh ich das! Bei der Definition der Variable s reserviere ich mehr "Platz", also:

volatile unsigned char s[9];
Bei der Verwendung von itoa bestimme ich dann in welcher Weise die Zahl angezeigt wird, für Binär:

itoa(read_data,s,2);
Es ist so einfach, wenn man weiß wie es geht :D
Danke für die Hilfe, dass wird mir in Zukunft nicht mehr passieren!

@Nik: Das sollte ich für die Zukunft tatsächlich mal überlegen. Jetzt ist es erstmal so aufgebaut, wie du es im Schaltplan sehen kannst. Meine Idee ist es mit Auslösung des Interrupts erstmal einen 10ms (ggf. +x ms) Timer zu starten. Nach dieser Zeit wird der PCF erst gelesen. Nur die häufig ausgelösten Interrupts (durch das Prellen) muss ich dafür abfangen.

Ich setze mich jetzt mal an eine verbesserte Version und werde die in nächster Zeit hier posten. Nochmal Danke für die Hilfe!!

Gruß, Reissdorf