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);
  }
}
 

Onderstaande code van het programma I²C scanner loopt alle 127 adressen van de bus af om te kijken welke chips er op de bus aanwezig zijn. Het adres van de gevonden chip wordt via het serieel kanaal getoond op de seriële monitor.

// --------------------------------------
// i2c_scanner
//
// Version 1
//    This program (or code that looks like it)
//    can be found in many places.
//    For example on the Arduino.cc forum.
//    The original author is not know.
// Version 2, Juni 2012, Using Arduino 1.0.1
//     Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26  2013
//    V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
//    by Arduino.cc user Krodal.
//    Changes by louarnold removed.
//    Scanning addresses changed from 0...127 to 1...119,
//    according to the i2c scanner by Nick Gammon
//    http://www.gammon.com.au/forum/?id=10896
// Version 5, March 28, 2013
//    As version 4, but address scans now to 127.
//    A sensor seems to use address 120.
// Version 6, November 27, 2015.
//    Added waiting for the Leonardo serial communication.
//
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.
//

#include <Wire.h>

void setup()
{
  Wire.begin();

  Serial.begin(115200);
  while (!Serial);             // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}

void loop()
{
  byte error, address;
  int nDevices;

  Serial.println("Scanning...");

  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknow error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000);           // wait 5 seconds for next scan
}