Forum: Compiler & IDEs I2C Bus schalten - Verhalten ?


von Achim S. (achims)


Lesenswert?

Hallo Gemeinde
bei meinem I2C Bus Aufbau und Programm habe ich komisches Verhalten.

Hardware:
AT1284p, 16MHz, 2xPCF8574
8 Eingänge mit I2C Bus

Ausgänge:
8 Ausgänge mit I2C Bus

Programm:
1
while(1)  
2
  {                       // Hauptschleife
3
    i2c_write(0xff);      // Alle Pins des PCF auf 0
4
    i2c_start(adr1_r);    // Starte Lesezugriff
5
    d=i2c_readNak();      // Schreib Leseergebnis in d
6
    
7
    if (~d & 0x01)        // Taste 1
8
      {                   // Wenn T1 gedrückt ist...
9
        e = 0xfe;         // Angabe LED
10
        i2c_start(adr2_w);  // Schreibbefehl
11
        i2c_write(e);     // Schreibe e
12
        _delay_ms(100);   // 100ms warten
13
      }
14
    
15
    if (~d & 0x02)        // Taste 2
16
      {                   // Wenn T1 gedrückt ist...
17
        e = 0xfd;         // Angabe LED
18
        i2c_start(adr2_w);  // Schreibbefehl
19
        i2c_write(e);     // Schreibe e
20
        _delay_ms(100);   // 100ms warten
21
      }
22
    
23
    if (~d & 0x04)        // Taste 3        
24
      {                   // Wenn T1 gedrückt ist...
25
        e = 0xfb;         // Angabe LED
26
        i2c_start(adr2_w);  // Schreibbefehl
27
        i2c_write(e);     // Schreibe e
28
        _delay_ms(100);   // 100ms warten
29
      }
30
    
31
    if (~d & 0x08)        // Taste 4
32
      {                   // Wenn T1 gedrückt ist...
33
        e = 0xf7;         // Angabe LED
34
        i2c_start(adr2_w);  // Schreibbefehl
35
        i2c_write(e);     // Schreibe e
36
        _delay_ms(100);   // 100ms warten
37
      }    
38
    }
39
    _delay_ms(100);
40
    i2c_stop();
41
  }

Effekt:
Ich kann mit den Tasten T1 bis T4 ohne Probleme die Ausgänge 1 bis 4 
schalten.
Schalte ich aber gleichzeitig alle 4 Ausgänge, entsteht ein Lauflicht. 
Alle 4 Ausgäng schalten schnell hintereiander die Eingänge ein und aus. 
Habe in jeder Abfrage nochmal eine Pause zugeordnet. Verursacht 
wahrscheinlich diesen Effekt.
Verwende ich einen Port vom Proz. kann ich es ohne Probleme abfragen und 
entsprechend veranlassen, das er nicht geschaltet wird.
Wie kann ich das beim I2C Bus machen?
achim

von Markus (Gast)


Lesenswert?

Hi,

überleg doch mal was
   i2c_write(e);
hintereinander mit e =
   0b11111110
   0b11111101
   0b11111011
   0b11110111
für die jeweilige LED bewirkt.

Du mußt Dir schon merken, welche LED wie geschaltet ist und nicht 
einfach den Zustand einer einzelnen LED schalten, ohne den Zustand der 
anderen zu berücksichtigen, damit Du sie Dir nicht ubeabsichtigt selbst 
wieder ausschaltest

Grüße, Markus

von Achim S. (achims)


Lesenswert?

Hallo Markus
bin auch am denken wie es geht. Verfolge einen anderen Ansatz dazu.
Jeder Taster hat doch einen bestimmte Hex wert. Da könnte ich doch die 
Hex Werte der Taster addîeren und bekomme einen Wert der mir sagt, 
welche LED leuchten soll.
Wie addiere ich die hex in C? oder macht der Rechner es automatisch?
achim

von Markus (Gast)


Lesenswert?

Hi Achim,

Achim Seeger schrieb:
> bin auch am denken wie es geht.

merke Dir in einer Variable die LED-Zustände und schalte die jeweilige 
LED unabhängig von den anderen LEDs per Bit-Operator an oder aus:
http://home.fhtw-berlin.de/~junghans/cref/CONCEPT/bitwise.html
http://en.wikipedia.org/wiki/Bitwise_operations_in_C

Du brauchst auch nicht für jede Taste 4x denselben Code mit immer 
denselben Parametern und nur unterschiedlicher e-Zuweisung 
hinzuschreiben. So gestaltest Du Dein Programm nur unübersichtlich, 
fehleranfällig und wegen der Redundanz mühsam wartbar. Ermittle zuerst 
den Wert für "e" für alle Tasten und schreibe "e" dannach einmalig weg.

Ich könnt's Dir in C hinschreiben, aber dann lernst Du nix dabei.

Grüße, Markus

von Achim S. (achims)


Lesenswert?

Hallo Markus
das mir der Addition ist mir schon klar

int a=16;
int b=2;
c=a&b;
print c;

(ganz schnell geschrieben)
Was ich nicht weiss ist die hex Zahl. Habe es bisher nicht gebraucht
Kann ich das auch machen:

eT1 = 0x01;
eT2 = 0x02;
eg = eT1 & eT2; (Habe die Variablen anders geschrieben)

Ich gehe einfach davon aus, das jede Taste einen bestimmten hex Wert hat

T1 = 0x01
T2 = 0x02
T3 = 0x04
T4 = 0x08

Dadurch könnte sich beim drücken von z.B. von T1 und T2 0x03 ergeben.
Diesen Wert könnte ich direkt ausgeben.
Bei 8 mögöichen Tasten könnte sich ein anderer Wert ergeben
Das Programm wird dabei in 2 Teile zerlegt.
- Das erfassen aller Werte der Tasten und addition
- und die einmalge Ausgabe über den Bus
achim

von Achim S. (achims)


Lesenswert?

Hat keiner eine Idee wie ich das machen kann?

von Daniel F. (df311)


Lesenswert?

Achim Seeger schrieb:
> c=a&b;
1
c=a&b
ist etwas völlig anderes als
1
c=a+b

das erste ist ein bitweises und, das zweite ist eine (herkömmliche) 
addition

Achim Seeger schrieb:
> Was ich nicht weiss ist die hex Zahl. Habe es bisher nicht gebraucht
2 (dec) = 0x02 = 0b0010 = 002
dem rechner (oder controller) ist es komplett egal in welchem format du 
die zahl in deinen quellcode schreibst - der kennt eh nur 0 und 1. 
interessant werden solche unterschiede (fast) nur bei (vereinzelten) 
funktionsaufrufen, die bei parametern eine bestimmte basis erwarten, bei 
der ausgabe der zahlen auf einem bildschirm oder display und bei der 
kommunikation über verschiedene schnittstellen wenn das protokoll die 
übertragung von zahlen als text vorsieht.

von Markus (Gast)


Lesenswert?

Hi Achim,

Achim Seeger schrieb:
> Hat keiner eine Idee wie ich das machen kann?

Der Zustand der vier Taster am Taster-Portexpander kommt zwar als Byte 
an, ein einzelner Taster schaltet von diesen 8 Bit aber nur ein 
einzelnes Bit ein oder aus.
Der Zustand der vier LEDs am LED-Portexpander wird zwar als Byte 
geschrieben, eine einzelne LED repräsentiert aber nur ein einzelnes Bit 
von diesen 8 Bit.

Das Bit eines Tasters bildest Du mit Deiner Software auf das Bit 
derjenigen LED ab, die diesem Taster entsprechen soll. Du hast also 4 
Input-Bits, die Du auf 4 Output-Bits abbildest. Und damit Du nicht die 
drei anderen LEDs beeinflusst, wenn Du den Zustand einer einzelnen 
änderst, merkst Du Dir immer den Zustand aller LEDs, manipulierst diesen 
Zustand und sendest den an den LED-Portexpander.

Ein Bit setzt man am anschaulichsten z.B. mit
1
var |= 1 << 6
var sei ein uint8_t, 6 ist Bit 6. Bits fängt man bei 0 an zu bezeichen, 
also Bit 0 bis 7 eines Bytes.

Ein Bit löscht man z.B. mit
1
var &= ~(1 << 0)
Hier wird Bit 0 von var gelöscht.

Ob ein Bit gesetzt ist, prüft man z.B. mit
1
if( var & 1 << 7)
Hier wird Bit 7 geprüft. Der Ausdruck ist True, wenn es gesetzt ist, 
andernfalls False.
Solltest Du in den geposteten Links mal nachlesen. Daß Du das nicht von 
Heute auf Morgen kapierst, ist kein Beinbruch. Jeder fängt mal an.

Und nun prüfst Du das Input-Byte vom Taster-Portexpander einzeln auf 
alle vier Tasterbits ab und setzt in einer Variable pro Taster das Bit 
der LED, das diesem Taster entspricht, wenn dieser Taster gedrückt ist. 
Ist er nicht gedrückt, löschst Du das Bit dieser LED. Bei negativer 
Logik (ist bei Dir wohl der Fall), löschst Du das LED-Bit bei gedrückter 
Taste und setzt es, wenn die Taste nicht gedrückt ist.
Diese Variable speichert den Zustand der LEDs. Den sendest Du zum Schluß 
an den LED-Portexpander.
Diese Variable braucht noch einen Initialzustand. Bei negativer Logik 
(active low) und alle LEDs ausgeschaltet ist dieser 0xff (== 255 == 0377 
== 0b11111111), bei positiver Logik (active high) 0x00.
Diese Variable muß so lange leben, wie Du die Taster zum Schalten der 
LEDs benutzen willst, also vermutlich immer.

Active low: Bit = 0 -> LED an, Bit = 1 -> LED aus
Active high: umgekehrt

Welche LED zu welchem Bit gehört, siehst Du daran, an welchem Pin des 
Portexpanders es angeschlossen ist, und am Datenblatt des Portexpanders.

Grüße, Markus

von Achim S. (achims)


Lesenswert?

Hallo Markus
Danke für deine Hilfe. Habe es so gemacht und es geht sehr gut.
Der Code:
1
int16_t var;
2
var = 0xff;
3
int main(void)
4
  {                         // Hauptprogramm
5
    i2c_init ();
6
    i2c_start(adr1_w);      // Schreibbefehl für Device 1
7
    while(1)    
8
      {                     // Hauptschleife
9
        i2c_write(0xff);    // Alle Pins des PCF auf 0
10
        i2c_start(adr1_r);  // Starte Lesezugriff
11
        d=i2c_readNak();    // Schreib Leseergebnis in d
12
13
        if (~d & 0x01)      // Taste 1
14
        var &=~(1<<0);      // Wenn T1 gedrückt ist...
15
          else
16
        (var |=1<<0);
17
        
18
        i2c_start(adr2_w);  // Schreibbefehl
19
        i2c_write(var);     // Schreibe e
20
        _delay_ms(5);       // 100ms warten
21
    
22
      }
23
     i2c_stop();
24
  }
Damit kann ich den Ausgang 1 mit der Taste 1 einschalten bzw. 
ausschalten.
Du hst Recht, wieder was gelernt. Danke für deine Hilfe. Der Code für 
alle.
Es gibt wieder eine andere Frage dazu.
In einer anderen Version (Storenschalter) soll zuerst Ausgang 1 schalten 
und verzögert um ca 1 Sekunde später dann Ausgang 2 bei hoch. Bei runter 
nur Ausgang 1. Wie bekomme ich das am besten rein?
achim

von Markus (Gast)


Lesenswert?

Hallo Achim,

Achim Seeger schrieb:
> In einer anderen Version (Storenschalter) soll zuerst Ausgang 1 schalten
> und verzögert um ca 1 Sekunde später dann Ausgang 2 bei hoch. Bei runter
> nur Ausgang 1. Wie bekomme ich das am besten rein?
ich würde dazu einen Timer aufsetzen, der z.B. alle 10ms eine 
Callback-Funktion aufruft. So eine Timer-Funktionalität läßt sich schön 
kapseln (eigenes C-Modul oder eigene C++-Klasse) und muß, wenn sie 
einmal fertig ist, nicht mehr angefaßt werden. Sie kann so in anderen 
Anwendungen wiederverwendet werden. Dafür gibt's sicher schon fertige 
Libraries. Selber schreiben macht aber mehr Spaß und man lernt mehr 
dabei.

Die Callback-Funktion wird bei der Timer-Funktionalität registriert und 
übernimmt timer-relevante Anwendungsfunktionalität, damit in der 
jeweiligen Anwendung die Timer-Funktionalität nicht umgeschrieben werden 
muß sondern stabil bleiben kann. Das Zeitintervall für den zyklischen 
Aufruf der Callback-Funktion kann man konfigurierbar gestalten.

Bei Timern kommt eine Interrupt Service Routine (ISR) zum Einsatz. In 
dieser darf die Callback-Funktion nicht aufgerufen werden. Stattdessen 
wird die ISR z.B. nur ein Flag setzen (dann type modifier "volatile" 
nicht vergessen), das anzeigt, daß das Zeitintervall (die 10ms) 
abgelaufen ist. Die Main-Loop ruft u.a. die Timer-Funktionalität auf, 
die nachschaut, ob das Flag gesetzt ist und wenn ja, setzt es dieses 
zurück und ruft die Callback-Funktion auf.
Das Abfragen dieses Flags könnte man zwar direkt in der Main-Loop machen 
und auch die Callback-Funktion bräuchte es nicht. Stattdessen könnte man 
direkt in der Main-Loop die timer-relevante Anwendungsfunktionalität 
reinschreiben. Aber delegiert man das Abfragen des Flags an die 
Timer-Funktionalität und separiert die timer-relevante 
Anwendungsfunktionalität vom Rest der Anwendung (wenn denn noch was 
übrig bliebt), trennt man die Angelegenheiten voneinander, die 
funktional recht wenig oder gar nichts miteinander zu tun haben und 
führt die Angelegenheiten zusammen, die zusammengehören. So 
modularisiert man, was (eine) Grundlage für die Übersichtlicgkeit von 
Software ist.

Bei mir löst die ISR einen Timer-Event aus (meine Anwendungen sind 
Event-gesteuert). Die Main-Loop ruft bei mir nur meinen Event-Dispatcher 
auf. Wann sonst was zu tun ist, steuert der Timer, die vom Zeitintervall 
abhängigen Zähler, Tastendrücke, die timer-gesteuert alle 10ms abgefragt 
und entprellt werden und Key-Events auslösen, etc.

In der Callback-Funktion kannst Du alle 10ms auf Tastendruck abfragen 
und für "Hoch" nach Schalten von Ausgang 1 einen Zähler starten (Flag 
setzen oder Zähler auf 1 setzen). Ist die 1s abgelaufen (bei 10ms pro 
Aufruf wird bis 100 (Flag gesetzt) oder bis 101 gezählt (Zähler > 0)), 
wird der Zähler gestoppt (Flag zurücksetzen oder Zähler auf 0 setzen) 
und Ausgang 2 geschaltet. Den Zähler brauchst Du über Aufrufgrenzen der 
Callback-Funktion hinweg, kannst ihn also nicht in der Callback-Funktion 
deklarieren.
"Runter" schaltet halt nur Ausgang 1 ohne Zähler.

Mit delay() ginge das sicher auch, aber damit hat die CPU 1 Sekunde lang 
nix anderes zu tun, weil lahmgelegt.

Grüße, Markus

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.