PCF8574 czyli jak łatwo zwiększyć liczbę pinów w Arduino

Wstęp

Arduino z rodziny UNO posiada 14 pinów cyfrowych. Jest to wystarczająca ilość do większości prostych zastosowań z dziedziny automatyki. Zdarzają się jednak sytuacje, jak np. podłączenie wyświetlacza, które drastycznie tą liczbę mogą zmniejszyć.

Co wtedy robić? Można przesiąść się na 2 razy droższe Arduino MEGA i mieć ponad 50 dodatkowych pinów. Można też użyć rejestru przesuwnego 74HC595, który da dodatkowe 8 wyjść cyfrowych za cenę 2 zł i użycia 3 pinów cyfrowych. Można też użyć układu PCF8574 i mieć dodatkowe 8 pinów (lub nawet 128 łącząc więcej układów) wejścia/wyjścia o możliwościach przekraczających te w Arduino za cenę użycia 2 pinów analogowych.

Układ scalony PCF8574

  • Vcc – Pin zasilania, podłączany w Arduino do pinu 5V.

  • GND – Pin masy zasilania podłączany w Arduino do GND.

  • P0..P7 – Cyfrowe piny do własnego wykorzystania.

  • SDA – Sygnał danych magistrali I2C podłączany w Arduino do Analog In 4.

  • SCL – Sygnał zegara magistrali I2C podłączany w Arduino do Analog In 5.

  • A0, A1, A2 – Wybór adresu układu, jeśli używasz jednego układu, wszystkie można podłączyć do GND.

  • /INT – Zanegowany sygnał przerwania. Można go używać do wykrywania zmiany stanu na jednym z cyfrowych wejść.

Magistrala I2C

Układ PCF8574 komunikuje się z Arduino za pomocą magistrali I2C. Jest to synchroniczna magistrala szeregowa wykorzystywana powszechnie w sprzęcie RTV. Szeregowa oznacza, że bity są przesyłane jednym pinem po kolei – pinem SDA. Synchroniczna oznacza, że każdy wysłany bit jest zatwierdzany sygnałem na drugim pinie – SCL. Synchroniczność przyczynia się do zwiększenia prędkości komunikacji i eliminuje błędy transmisji.

Magistrala I2C ma też własny protokół komunikacji, dzięki któremu do jej sygnałów SDA/SCL można podłączyć więcej niż jeden układ scalony. Każdy układ w magistrali ma swój adres. Adres jest to liczba, która identyfikuje układ, wybierając tą liczbę masz pewność, że dane które wysyłasz trafią do właściwego układu. Układ PCF8574 ma dodatkowo piny A0, A1, A2 za pomocą których możesz konfigurować część adresu. Pozwala to na podłączenie do magistrali więcej takich samych układów scalonych, dzięki modyfikacji stanów logicznych na tych pinach. Trzy piny adresu oznaczają, że możesz podłączyć do magistrali 8 takich układów scalonych, co da 64 dodatkowe piny cyfrowe. Jeśli i to jest za mało to istnieje też wersja układu PCF8574A, która ma zmieniony adres, co sprawia, że oprócz tamtych 8 można podłączyć jeszcze 8 układów i mieć w sumie 128 pinów.

W Arduino magistrala I2C ze względu na problemy licencyjne nazywa się TWI (TwoWires [dwa przewody] – od liczby przewodów używanych przez nią). Mikrokontroler AVR zawarty na płytce zawiera sprzętową obsługę tej magistrali. Jej wyprowadzenia znajdują się na dwóch ostatnich pinach grupy “ANALOG IN”. Sygnał danych SDA znajduje się na pinie 4, a sygnał zegarowy SCL na pinie 5.

Adres układu PCF8574

Układy z rodziny PCF8574 mają 7 bitowy adres. Pierwsze 3 bity adresu nadaje mu użytkownik, przez ustawienie pinów A0, A1, A2. Kolejne 4 są nadane fabrycznie na stałe. Układ PCF8574 ma je ustawione na 0100, a układ PCF8574A ustawione na 0111.

Binarny adres układu PCF8574

0

1

0

0

A2

A1

A0

Co oznacza, że minimalny adres (dla wszystkich A = LOW) to dziesiętnie 32, szesnastkowo 0x20. Maksymalny adres (dla wszystkich A = HIGH) to dziesiętnie 39, a szesnastkowo 0x27.

Binarny adres układu PCF8574A

0

1

1

1

A2

A1

A0

Co oznacza, że minimalny adres (dla wszystkich A = LOW) to dziesiętnie 56, szesnastkowo 0x38. Maksymalny adres (dla wszystkich A = HIGH) to dziesiętnie 63, a szesnastkowo 0x3F.

Podłączenie do Arduino

Podłączenie nie sprawia trudności i wykorzystuje tylko 4 przewody. Vcc układu łącze z pinem 5V Arduino, GND układu łącze z pinem GND Arduino w sekcji zasilania. Sygnały SDA łącze z pinem 4, a SCL z pinem 5 sekcji “ANALOG IN” Arduino.

Ponieważ używam w tej konfiguracji tylko jednego układu, podłączyłem wszystkie linie adresowe w stan niski (LOW) łącząc je z GND. Jeśli w twojej konfiguracji jest więcej takich samych układów, powinieneś do linii adresowych stosować różne kombinacje stanów (LOW – GND, HIGH – 5V) innych dla każdego układu. Pozostałe piny P0 do P7 możesz wykorzystywać dowolnie jako piny cyfrowe (DIGITAL), jak to robisz w Arduino.

Programowanie układu PCF8574

Przygotowanie

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void setup()
{
  expander.begin(0x20);
}

void loop()
{
}

Jak zawsze społeczność Arduino nie zawiodła i jeden z miłośników platformy przygotował odpowiednią bibliotekę. Aby rozpocząć komunikacje z układem PCF8574 musisz najpierw podłączyć 2 biblioteki – PCF8574.h (dostarczającą funkcje obsługi układu) i Wire.h (do obsługi magistrali TWI).

Następnie trzeba zadeklarować obiekt klasy “PCF8574”, który nazwałem “expander”. Deklaruje się to jak inne zmienne czyli “PCF8574 expander;”.

Potem w funkcji “setup” należy za pomocą metody “expander.begin” ustawić adres układu, który ma reprezentować zadeklarowany obiekt. W tym przypadku ustawiłem wartość szesnastkową 0x20 odpowiadającą układowi z liniami adresowymi w stanie “LOW” – jak na schemacie.

Ustawienie kierunku działania pinów

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void setup()
{
  expander.begin(0x20);
  expander.pinMode(0, OUTPUT);
  expander.pinMode(1, INPUT);
}

void loop()
{
}

Nazwy funkcji biblioteki obsługującej układ zostały tak dobrane, by przypominać te z biblioteki obsługującej piny Arduino. z tym, że zamiast “pinMode” jest “expander.pinMode”, gdyż dotyczy obiektu układu. Działanie funkcji jest bliźniaczo podobne do tych w Arduino. Pierwszy argument to numer pinu układu od 0 do 7, a drugi to kierunek działania pinu “INPUT” – wejście, “OUTPUT” – wyjście.

W tym przykładzie ustawiłem pin 0 na wyjście, a pin 1 na wejście.

Ustawienie stanu pinów

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void setup()
{
  expander.begin(0x20);
  expander.pinMode(4, OUTPUT);
}

void loop()
{
  expander.digitalWrite(4, LOW);
  delay(1000);
  expander.digitalWrite(4, HIGH);
  delay(1000);
}

Podobnie jak w Arduino, tak i tutaj do ustawienia stanu pinów służy bliźniacza funkcja “expander.digitalWrite”. Jej pierwszym argumentem jest numer pinu od 0 do 7, a kolejnym stan jaki ma być ustawiony. “LOW” to stan niski odpowiadający GND, “HIGH” to stan wysoki odpowiadający 5V. Oczywiście można ustawiać tylko stan pinów wyjściowych “OUTPUT”.

Ten przykład co sekundę zmienia stan pinu 4 układu.

Jeśli teraz podłączysz do pinu 4 (noga układu 9) rezystor 220 Ohm, a drugie wyprowadzenie rezystora do katody diody LED, potem anodę diody LED do zasilania 5V, to powinna zgodnie z programem mrugać.

Odwracanie stanu pinów wyjściowych

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void setup()
{ 
  expander.begin(0x20);
  expander.pinMode(4, OUTPUT);
}

void loop()
{
  expander.toggle(4);
  delay(1000);
}

Przykład z poprzedniego rozdziału można odrobinę skrócić. Biblioteka PCF8574 zawiera dodatkową funkcję “expander.toggle”, która automagicznie odwraca stan na wyjściu układu na przeciwny. Argumentem metody jest numer pinu od 0 do 7, którego stan chcesz odwrócić.

Przykładowy program robi to samo co poprzedni z tym, że krócej.

Mruganie

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void setup()
{ 
  expander.begin(0x20);
  expander.pinMode(4, OUTPUT);

  expander.blink(4, 10, 500);
}

void loop()
{
}

Metoda “expander.blink” odpowiada za mruganie stanem pinu. Jej pierwszy argument to numer pinu od 0 do 7, drugi argument to ilość mrugnięć, a trzeci, czas w jakim ma trwać jeden stan mrugania w milisekundach.

Przykład sprawia, że na pinie 4 (noga 9) pojawi się 10 impulsów – naprzemiennych stanów LOW – HIGH. Każdy stan będzie trwał 500 milisekund, czyli 0,5 s.

Odczytywanie stanu pinów

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

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

  expander.begin(0x20);
  expander.pinMode(0, INPUT);
}

void loop()
{
  byte value = expander.digitalRead(0);

  Serial.println(value, DE);
  delay(100);
}

Odczyt stanu pinu ustawionego na wejście “INPUT” odczytujesz za pomocą “expander.digitalRead”. Jedynym argumentem tej metody jest numer pinu od 0 do 7.

Powyższy przykład odczytuje wartość pinu 0 (noga 4) i zapisuje go do zmiennej “value”. Jeśli na pinie 0 jest stan niski, to zmienna “value” będzie miała wartość “LOW” (0), a jeśli wysoki, to zmienna będzie miała wartość “HIGH” (1). Wartość zmiennej jest wysyłana do komputera (dzięki Serial.println), dzięki czemu możesz ją podglądać w Serial Monitor z Arduino IDE.

Rezystory PullUp wejść układu PCF8574

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

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

  expander.begin(0x20);
  expander.pinMode(4, INPUT);
  expander.pullUp(4);
}

void loop()
{
  byte value = expander.digitalRead(4);

  Serial.println(value, DEC);
  delay(100);
}

Wejścia cyfrowych układów scalonych mogą mieć 3 stany. LOW – wejście ustawione w stan niski np. przez podłączenie do GND, HIGH – wejście ustawione w stan wysoki np. podłączone do 5V i stan nieustalony. Stan nieustalony jest wtedy, kiedy w przewodzie wejścia cyfrowego nie ma żadnego konkretnego stanu wysyłanego przez inne podłączone układy. Stan nieustalony jest interpretowany przez wejście układu jako stan losowy w zależności od zakłóceń ustawiany raz na LOW, a raz na HIGH.

Wiele elementów jak np. przełącznik, albo przewodzą prąd, albo nie. Jeśli podłączysz przełącznik do wejścia cyfrowego, to gdy nie będzie przewodził, będzie stan nieustalony i układ może dostawać błędne sygnały.

Rezystory pullup wymyślono po to, aby gdy wejście nie ma ustalonego stanu, same ustalały stan domyślny, dopóki na wejściu nie pojawi się konkretny stan. Pullup oznacza “podciągnij w górę”, znaczy to, że domyślnym stanem niepodłączonego wejścia będzie stan wysoki “HIGH”. Dlatego na schemacie podłączyłem przełącznik do pin 4 układu, a jego drugie wyprowadzenie do GND. Wobec czego gdy przełącznik nie przewodzi, dzieki pullup jest domyślnie stan wysoki. Gdy przełącznik przewodzi, zwiera wejście do GND przez co ustala stan na niski “LOW”.

Za ustalenie domyślnie stanu wysokiego na wejściu układu PCF8574 odpowiada funkcja “expander.pullUp”, której argumentem jest numer pinu układu od 0 do 7.

Przykład programu ustawia pullup na wejście numer 4 (noga 9) i wysyła jego stan do komputera.

Rezystory Pull Down układu PCF8574

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

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

  expander.begin(0x20);
  expander.pinMode(4, INPUT);
  expander.pullDown(4);
}

void loop()
{
  byte value = expander.digitalRead(4);

  Serial.println(value, DEC);
  delay(100);
}

Układ PCF8574 jest na tyle uniwersalny, że zapewnia też rezystory pulldown. Ustalają one domyślnie wejście cyfrowe na stan niski “LOW”. Zatem teraz podłączenie wejścia do 5V ustala jego stan na wysoki “HIGH”, a brak stanu to domyślnie stan niski.

Równoległy dostęp do pinów

Równoległy dostęp do pinów to funkcje dzięki którym za jednym wywołaniem program może obsłużyć wszystkie piny układu PCF8574 na raz.

  • expander.write(value)
    Pozwala ustawić wartość binarną argumentu “value” na wszystkich pinach wyjściowych układu.
  • expander.read()
    Pozwala odczytać wartość binarną wszystkich pinów.
  • expander.clear()
    Ustawia na wszystkich wyjściowych pinach stan niski “LOW”.
  • expander.set()
    Ustawia na wszystkich pinach stan wysoki “HIGH”.
#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void setup()
{ 
  expander.begin(0x20);
  for (byte i=0; i<8; i++)
    expander.pinMode(i, OUTPUT);
}

void loop()
{
  byte value = random(0, 255);

  expander.write(value);
  delay(100);
}

Przykład ustawia wszystkie piny jako wyjścia i zmienia ich stan losowo.

Przerwania – układ elektryczny

Układ PCF8574 na nodze 13 posiada sygnał /INT. Jest to sygnał przerwania, informujący Arduino, “że coś się dzieje”, bez potrzeby ciągłego sprawdzania stanu pinu układu. Sygnał ten wykorzystywany jest do tego, że gdy na pinie wejściowym pojawi się odpowiedni stan, sygnał /INT ustawia się w stan niski. Sygnał ten jest w standardzie “otwarty dren”. Oznacza to, że jego wyjście ma albo stan niski, albo nieustalony. Umożliwia to podłączenie do tego samego pinu Arduino wyjść przerwań z większej ilości układów PCF8574, gdyż nie następuje kolizja stanów logicznych.

Sygnał przerwania /INT możesz podłączyć do dowolnego pinu cyfrowego Arduino. Ja wybrałem pin numer 2, do którego prowadzi niebieski przewód.

Przerwania – program podstawowy

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void onInterrupt()
{
  Serial.println("Przerwanie");
}

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

  expander.begin(0x20);

  expander.pinMode(4, INPUT);
  expander.pullUp(4);

  pinMode(2, INPUT);
  digitalWrite(2, HIGH);

  expander.enableInterrupt(2, onInterrupt);
}

void loop()
{
}

W przykładzie ustawiłem pin 4 układu PCF8574 jako wejście i włączyłem mu domyślny stan wysoki (“pullUp”).

Następnie ustawiłem pin 2 Arduino na wejście. Ponieważ wyjście /INT może mieć albo stan niski, albo nieustalony, ustawiłem wejście pin 2 Arduino też jako “pullup” za pomocą “digitalWrite”.

Za pomocą metody “expander.enableInterrupt” wyberasz pin w Arduino do którego przyłączyłeś sygnał /INT układu (pierwszy argument) i rejestrujesz funkcję, która będzie automatycznie wywoływana po nastąpieniu przerwania (drugi argument). Ja zarejestrowałem funkcje o nazwie “onInterrupt”, która wysyła do komputera napis “Przerwanie”, gdy zostanie wywołana.

Po uruchomieniu programu, zmiana stanu pinu 4 układu (noga 9) na przeciwny niż domyślny, powoduje uruchomienie zarejestrowanej funkcji. Jak widzisz bez żadnego sprawdzania stanu pinów można dzięki przerwaniu automatycznie wywoływać funkcje. To rozwiązanie ma jedną wadę. Tą samą funkcję wywołuje dowolny pin wejściowy układu.

Powiązanie przerwania z funkcją można zlikwidować za pomocą funkcji “expander.disableInterrupt”, która nie ma żadnego argumentu.

Przerwania – program rozszerzony

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander;

void onPin0()
{
  Serial.println("Pin 0");
}

void onPin1()
{
  Serial.println("Pin 1");
}

void onInterrupt()
{
  Serial.println("Przerwanie");
  expander.checkForInterrupt();
}

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

  expander.begin(0x20);

  expander.pinMode(0, INPUT);
  expander.pullUp(0);
  expander.pinMode(1, INPUT);
  expander.pullUp(1);

  pinMode(2, INPUT);
  digitalWrite(2, HIGH);

  expander.enableInterrupt(2, onInterrupt);

  expander.attachInterrupt(0, onPin0, FALLING);
  expander.attachInterrupt(1, onPin1, FALLING);
}

void loop()
{
}

Jeśli w funkcji obsługującej przerwanie (“onInterrupt”) umieścisz wywołanie metody “expander.checkForInterrupt”, biblioteka zdobędzie nowe możliwości.

Od tej chwili możesz używać metod “expander.attachInterrupt” do rejestrowania funkcji wywoływanych osobno dla każdego pinu układu PCF8574. Ułatwia to pisanie nowych programów.

Metoda “expander.attachInterrupt” ma 3 argumenty. Pierwszym z nich jest numer pinu wejściowego układu, do którego chcesz podłączyć swoją funkcję. Kolejny argument to nazwa funkcji, która chcesz podłączyć. Ostatnim jest rodzaj zdarzenia jaki ma wywoływać funkcję. Istnieją 4 rodzaje zdarzeń.

  • CHANGE – wywołuje funkcje przy każdej zmianie stanu pinu

  • LOW – wywołuje funkcje jeśli pin układu ma stan niski

  • FALLING – wywołuje funkcję przy zmianie stanu z wysokiego na niski

  • RISING – wywołuje funkcje przy zmianie stanu z niskiego na wysoki

W przykładzie ustawiłem piny 0 i 1 układu na wejścia o domyślnym stanie wysokim. Potem zarejestrowałem dla nich funkcje “OnPin0” i “OnPin1” wywoływane podczas zmiany stanu z wysokiego “HIGH” na niski “LOW” (“FALLING”).

Powiązanie pinu z funkcją można zlikwidować za pomocą funkcji “expander.detachInterrupt”. Jej jednym parametrem jest numer pinu układu.

Podłączenie większej ilości układów PCF8574

Układ elektryczny

Włączając w obwód dodatkowe układy PCF8574 należy połączyć ich linie sygnałowe i zasilania. SDA, łączysz z SDA pierwszego układu. SCL łączysz z SCL pierwszego układu. /INT łączysz z /INT pierwszego układu. To samo z zasilaniem. 5V łączysz z 5v, a GND z GND.

Nieco inaczej ma się sprawa z liniami adresowymi. Tu należy ustawić binarnie inny adres niż układu pierwszego. W przykładzie podłączyłem linię A0 do 5V – czyli wymusiłem na niej stan wysoki “HIGH”. A1 i A2 zostawiłem bez zmian, czyli podłączone do GND. Sprawia to, że adres drugiego układu zwiększył się o 1. Teraz pierwszy układ ma adres szesnastkowy 0x20, a drugi 0x21.

Programowanie

#include <PCF8574.h>
#include <Wire.h>

PCF8574 expander1;
PCF8574 expander2;

void onInterrupt()
{
  expander1.checkForInterrupt();
  expander2.checkForInterrupt();
}

void setup()
{

  expander1.begin(0x20);
  expander2.begin(0x21);

  pinMode(2, INPUT);
  digitalWrite(2, HIGH);

  expander1.enableInterrupt(2, onInterrupt);
}

void loop()
{
}

Po dodaniu drugiego układu program też trzeba zmodyfikować. Teraz każdy układ reprezentują 2 niezależne obiekty – expander1 i expander2. Każdy z obiektów trzeba połączyć z innym układem przez adres danego układu. Zajmuje się tym metoda “begin” w funkcji “setup”. Jak ustaliłem na schemacie jeden układ ma adres 0x20, a kolejny 0x21. W funkcji “onInterrupt” trzeba rozdzielić metodę “checkForInterrupt” na 2 układy. Cała reszta programowania jest taka sama, z tym, że należy pamiętać który pin należy do którego układu i co za tym idzie do którego obiektu. Piny w obydwu układach liczone są od 0 do 7.

Inne rodzaje Arduino

Arduino MEGA

W Arduino MEGA magistrala TWI wyprowadzona jest w nieco innym miejscu. Dlatego jeśli jesteś użytkownikiem tej płytki to musisz nieco zmodyfikować schemat połączeń. Sygnał SDA jest tam na pinie 20, a SCL na pinie 21 z grupy “COMMUNICATION”.

Arduino R3

Nadchodzące wersje Arduino z serii R3 mają powiększoną ilość pinów. Powyższe schematy nie wymagają modyfikacji, jednak dla wygody użytkowników wyprowadzono tam sygnały TWI w dodatkowe miejsce. Znajdują się one w grupie “DIGITAL” na lewo od pinu “AREF”. Pierwszy z nich licząc od lewej to SCL, a następny to SDA.

Zdjęcie Arduino UNO R3 należy do zespołu Arduino (www.arduino.cc). Zostało pobrane z http://arduino.cc/en/Main/ArduinoBoardUno i zmodyfikowane.

Zakończenie

Teraz jeśli brak ci pinów masz już o jedno rozwiązanie więcej. Jest to rozwiązanie o tyle elastyczne, że bez problemu w razie potrzeb możesz dokładać i dokładać, aż do 128 pinów.

Zatem zapraszam do lutownicy i życzę nowych oraz ekscytujących projektów. No i nie zapomnij się nimi pochwalić.

Linki