SevenSeg w NodeMCU z małą pomocą PCF8574

Realizując swój projekt oparty o ESP8266 (prototypowany na NodeMCU) natrafiłem na pewien problem. Zabrakło pinów cyfrowych, aby urządzenie mogło realizować swoje podstawowe cele i dodatkowo sterować siedmio semgentowym wyświetlaczem składającym się z 4 cyfr. Pierwsza myśl: spoko, podłączę expander PCF8574, użyję biblioteki i będzie działało. Otóż nie. Okazało się, że po dołączeniu chyba najbardziej popularnej biblioteki ( https://github.com/skywodd/pcf8574_arduino_library/tree/master/PCF8574 ) projekt się nie kompiluje. Co zrobić w takiej sytuacji? Zapraszam do dalszej części artykułu.

Doszedłem do wniosku, że szybciej niż analizować czyjś kod wyjdzie mi bezpośrednie skomunikowanie się z expanderem za pomocą magistrali I2C (TWI). Tak też właśnie zrobiłem. Przeprowadzając krótki research w internecie okazało się, że był to strzał w dziesiątkę, gdyż podstawowy odczyt / zapis stanów logicznych z PCF8574 jest bajecznie prosty.

Pierwsze, co musimy zrobić to dołączenie biblioteki Wire.h. Umożliwi nam ona komunikację przez I2C.

#include <Wire.h>

W funkcji setup() musimy zainicjować magistralę poprzez wywołanie metody begin() klasy Wire.

Wire.begin();

Ustawienie stanu pinów

Aby ustawić stan pinów musimy nawiązać rozpocząć transmisję danych z ekspanderem o danym adresie. Mój ma ustawiony adres 0x20.

Wire.beginTransmission(0x20);

Następnie przesyłamy bajt danych (8 bitów). Każdy bit odpowiada konkretnemu pinowi I/O expandera. Wysyłając ciąg ośmiu zer ustawimy wszystkie piny na LOW, natomiast wysyłając same jedynki wszystkie piny będą w stanie HIGH.

Wire.write(0b11111111);

Następnie kończymy transmisję danych wywołując metodę endTransmission.

Wire.endTransmission();

Odczytywanie stanu pinów

Odczytane stanu pinów jest równie łatwe. Wystarczy wywołać metodę requestFrom(addr, quantity), gdzie addr to adres urządzenia, a quantity to ilość bajtów, które chcemy pobrać (w naszym przypadku 1 bajt)

Wire.requestFrom(0x20, 1);

Teraz możemy odczytać dane pod warunkiem, że ostały nam one wysłane z powrotem:

byte data;

if(Wire.available()) {
        data = Wire.receive();
}

Teraz każdy bit w zmiennej data odpowiada stanowi kolejnych pinów expandera.

Wysterowanie 4 cyfr wyświetlacza (multiplexing)

Będziemy potrzebowali NodeMCU, 4 cyfrowego wyświetlacza 7seg (lub 4 wyświetlacze jedno cyfrowe) ze wspólną anodą oraz expander. Kod programu możemy pobrać TUTAJ.

Anody cyfr podłączamy do pinów cyfrowych NodeMCU, natomiast katody oznaczone literami od A do H do pinów expandera (przez odpowiednie rezystory, ja użyłem 220 Ohm). Piny adresowe expandera możemy zewrzeć do masy, dzięki czemu jego adres będzie równy 0x20.

W NodeMCU pin SCL jest tym samym pinem, co D1, natomiast SDA to D2.

W moim przypadku podłączyłem kolejne katody wyświetlacza do kolejnych pinów expandera. Połączenie powinno być dobrze widoczne na załączonym obrazku. Anody jak widać po kodzie podłączyłem do pinów D8, D5, D6 oraz D7.

Omówienie kodu

W funkcji loop() jedyne co się dzieje to zwiększanie wartości wyświetlanej na wyświetlaczu co jakiś czas oraz zadbanie o to, aby wykonała się kluczowa dla działania programu funkcja displayNumber.

void displayNumber(float number) {
  int num = (int) (number * 10);
  byte numIndex[4] = {0};
  for(int x = 3; x >= 0; x--) {
    numIndex[x] = num%10;
    num /= 10;
  }
  if(numIndex[0] == 0) numIndex[0] = 10;
  
  for(int x = 0; x < 4; x++) {
    byte code = digitCodes[numIndex[x]];
    if(x == 2) {
      code |= 1 << 2;
    }

    Wire.beginTransmission(ADDR);
    Wire.write(0b11111111);
    Wire.endTransmission();
    
    for(int y = 0; y < 4; y++) {
      digitalWrite(DIGIT[y], x == y);
    }
    
    Wire.beginTransmission(ADDR);
    Wire.write(~code);
    Wire.endTransmission();
    
    delay(3);
  }
}

Mój wyświetlacz będzie wyświetlał liczbę rzeczywistą z zakresu 0-999.9. Na początku więc zapisujemy sobie do tablicy kolejne cyfry naszej liczby. Następnie jeżeli najstarsza liczba jest zerem, to nie ustawiamy ją na 10, aby nie wyświetlała się (po co?).

Następnie otwieramy pętlę for, która będzie sterowała wyświetlaczem. Wykona się ona 4 razy (dla każdej cyfry podświetli inny zestaw diod). Obliczamy kod binarny dla naszej liczby. Jeżeli jest to trzecia cyfra (druga, indeksując od 0) to drugi bit, który jest u nas podłączony do kropki ustawiamy na 1.

Teraz gasimy wszystkie diody, przełączamy stan na anodach wybierając tą, którą w aktualnej iteracji chcemy wysterować i wysyłamy do expandera kod binarny, który zapali nam odpowiednie diody.

Zauważyliście pewnie tyldę (~) przed zmienną code w metodzie write(). Łatwiej było mi oznaczyć diody, które mają świecić za pomocą jedynki a te, które mają być zgaszone za pomocą zera. Jako, że dioda będzie świeciła przy stanie niskim, musimy zanegować wszystkie bity.

Na końcu odczekujemy 3 ms, ponieważ gdyby cyfry przełączały się zbyt szybko to mogłyby się świecić te diody, których aktualnie nie potrzebujemy.

W ten oto sposób wysterowaliśmy 4 cyfry wyświetlacza 7-segmentowego. Bez problemu możemy dokładać kolejne i kolejne, aż skończą nam się adresy kolejnych ekspanderów :)