Oplossing drukknoppen gevorderd op I2C

Bij dit programma sluiten we 4 drukknoppen aan op P4..P7 van de PCF8574, en 4 leds op de P0..P3 van de I/O expander.

In het programma zetten we initieel alle uitgangen van PCF8574 hoog. Daardoor gaan de 4 leds, die in common anode configuratie aangesloten zijn uit.

Daarna lezen we de 8 lijnen van de PCF8574. We schuiven de 8 bits van het resultaat 4 posities op naar rechts, en sturen dit resultaat terug naar de PCF8574.

Het programma ziet er als volgt uit:

#include <Wire.h>

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(115200);  // start serial for output
}

void loop() {
  Wire.beginTransmission(0x38);
  Wire.write(0xFF);
  Wire.endTransmission();
 
  Wire.requestFrom(0x38, 1);    // vraag 1 byte aan de PCF8574
  while (Wire.available()) { // wacht tot byte beschikbaar is
    byte c = Wire.read();    // lees de waarde
    Wire.beginTransmission(0x38);
    Wire.write(c>>4);
    Wire.endTransmission();
  }
  delay(50);
}

Merk op dat wanneer een knop ingedrukt is, de bijhorende led gaat branden zolang de knop ingedrukt blijft, maar dat bij elke doorgang van de loop()-functie gedurende een fractie van een milliseconde de lijnen terug op 1 zullen gezet worden en alle leds dus zullen doven. Dit gaat echter zodanig snel dat we dit niet merken, en het lijkt alsof de led continu brandt.

 

Oplossing drukknoppen op I2C

Wanneer op de I/O-pinnen van een PCF8574 een verandering optreedt, zal het INT-signaal van de PCF8574 laag worden. We verbinden dit signaal met pin 4 van de Leonardo om te kunnen vaststellen dat er een wijziging gebeurd is op de PCF8574, en dat de nieuwe toestand moet worden ingelezen via de I2C bus.

#include <Wire.h>

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(115200);  // start serial for output
  pinMode(4,INPUT_PULLUP);
}

void loop() {
  // zet alle uitgangen van de PCF8574 hoog
  Wire.beginTransmission(0x38);
  Wire.write(0xFF);
  Wire.endTransmission();

  // wacht tot de INT pin laag wordt
  while(digitalRead(4)) {}
  Wire.requestFrom(0x38, 1);    // vraag 1 byte aan de PCF8574

  while (Wire.available()) { // wacht tot byte beschikbaar is
    byte c = Wire.read();    // lees de waarde
    Serial.println(c,BIN);     // print de waarde binair
  }
}

In de loop()-functie zetten we eerst alle I/O lijnen van de PCF8574 hoog. Daarna wachten we tot pin 4, die verbonden is met het INT-signaal van de PCF8574 laag wordt.

Als dit gebeurd is vragen we de PCF8574 om de waarde op de I/O pinnen door te sturen. We wachten met de Wire.available() functie tot de PCF8574 zegt dat hij klaar is om de waarde te sturen, en lezen die dan met de Wire.read()-functie.

Daarna zenden we de nieuwe waarde in binaire vorm naar de seriële terminal.

 

Oplossing volloper op I2C

We passen het looplichtprogramma aan door het patroon aan te passen voor een vollopereffect naar links. We hebben ook een tweede patroon nodig om het rechtse effect te realiseren. Merk op dat de arrays nu beide eindigen op 0xFF, zodat na het effect alle leds ook weer zouden gedoofd worden.

Daarna sluiten we drukknoppen aan op pinnen 4 en 5. We kiezen voor 4 en 5 omdat 0 en 1 dienen voor seriële communicatie en we deze optie willen open houden voor het geval we later ons programma willen uitbreiden met seriële communicatie.

Ook pinnen 2 en 3 mogen we niet gebruiken, omdat dit op de Leonardo de I2C pinnen zijn, die extra naar buiten gebracht zijn via SCL en SDA.

We definiëren pinnen 4 en 5 als INPUT_PULLUP. Daarmee wordt intern een pullup weerstand ingeschakeld, die het niveau van de pin met de Vcc verbindt.In normale toestand staat er op deze pinnen dus een hoog niveau. Met de drukknoppen kunnen we dit niveau naar massa trekken, waardoor er een 0 op de ingang komt.

Met een digitalRead() lezen we het niveau van de pin. We kunnen dit niveau inverteren met de !-operator. Daardoor wordt de uitdrukking

if(!digitalRead(4)&&digitalRead(5))

gelezen als: indien drukknop op pin 4 ingedrukt is, en drukknop op pin 5 niet ingedrukt is.

We gebruiken deze voorwaarde om te bepalen dat de ene drukknop ingedrukt is, en de andere niet.

In een tweede if-constructie testen we de omgekeerde situatie.

De volledige oplossing wordt:

#include <Wire.h>

const byte patroonlinks[]={0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 0x00,0xFF};
const byte patroonrechts[]={0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00,0xFF};
void setup() {
  pinMode(4,INPUT_PULLUP);
  pinMode(5,INPUT_PULLUP);
  Wire.begin();
}


void loop() {
  if(!digitalRead(4)&&digitalRead(5)) {
    for(int i=0;i<sizeof(patroonlinks);i++) {
    Wire.beginTransmission(0x38);
    Wire.write(patroonlinks[i]);
    Wire.endTransmission();
    delay(50);
    }
  }
    if(digitalRead(4)&&!digitalRead(5)) {
    for(int i=0;i<sizeof(patroonrechts);i++) {
    Wire.beginTransmission(0x38);
    Wire.write(patroonrechts[i]);
    Wire.endTransmission();
    delay(50);
    }
  }
}
 

Oplossing: looplicht op PCF8574

Dingen om mee rekening te houden:

PCF8574 pin layout

In bovenstaande afbeelding zien we dat de 8 I/O pinnen aangeduid worden met P0..P7. Verder hebben we de SDA en SCL pinnen die de I²C bus vormen, en de Vcc en GND voor de voeding. Blijven nog over de INT pin, die wijzigt zodra de toestand op P0..P7 wijzigt, en kan gebruikt worden om aan de controller aan te geven dat de nieuwe waarden voor P0..P7 moeten ingelezen worden, en A0..A2, de pinnen die deel uitmaken van het adres van de chip.

Bij I²C wordt gebruik gemaakt van een 7-bit adres om een chip op de bus te identificeren. Deze adressen liggen vast per type chip. Voor de PCF8574 ligt dat adres vast op

PCF8574 I²C adres
0 1 1 1 A2 A1 A0


In hexadecimale notatie geeft dit 0X38 wanneer A2, A1 en A0 allemaal 0 zijn. Als A2..A0 met de massa zijn verbonden zal het adres van deze PCF8574 dus 38h zijn.Stel dat we nog een tweede PCF8574 op dezelfde I²C bus willen gebruiken, dan kunnen we voor deze nieuwe chip A0 aan de Vcc hangen, terwijl A2 en A1 met de massa verbonden blijven. Deze tweede PCf8574 heeft dan als adres 39h.

Met 3 in te stellen adresbits kunnen we 8 unieke adressen maken van 38h tot 3Fh. We kunnen op één I²C bus dus maximaal 8 PCF8574 I/O expanders plaatsen, wat ons een bijkomende 64 I/O pinnen geeft. Al deze I/O wordt vanuit de Arduino gestuurd d.m.v. 2 lijnen: SDA en SCL.

Waar ook mee moet rekening gehouden worden, is het vermogen van de pinnen. In de datasheet vinden we mij de absolute maximum ratings het volgende:

Dit betekent dat een uitgang maximaal 4 mA kan sourcen (=leveren als bron), en 50mA kan sinken (=binnenhalen).

Aangezien een led minimaal een 10-tal mA nodig heeft om te branden moeten we bij leds gebruik maken van de common-anode configuratie. Een voorbeeld van de schakeling is te zien in onderstaande afbeelding:

Om een looplicht te maken op de PCF8574 moeten er 8 opeenvolgende bytes via de I²C bus gestuurd worden. Door de common anode configuratie zijn deze waarden FEh, FDh, FBh, F7h, EFh, DFh, BFh en 7Fh. We kunnen eigenlijk hetzelfde programma als van het looplicht op het schuifregister gebruiken, alleen moeten we nu sturen naar de I²C bus i.p.v. het schuifregister.

#include <Wire.h>

const byte patroon[]={0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F};

void setup() {
  Wire.begin();
}


void loop() {
  for(int i=0;i<sizeof(patroon);i++) {
  Wire.beginTransmission(0x38);
  Wire.write(patroon[i]);
  Wire.endTransmission();
  delay(500);
  }
}