Sterowanie wyświetlaczem siedmiosegmentowym. Programowanie strukturalne.

Wyświetlacze siedmiosegmentowe (sevseg).

Na dzisiejszym spotkaniu poznaliśmy jedno z wielu praktycznych zastosowań LEDów. Siedmiosegmentowe wyświetlacze są układami diod emitujących światło w odpowiednim ustawieniu umożliwiającym wyświetlanie cyfr. Wyświetlacze te, nazywane sevseg (ang. seven sevseg

segment display) są wszechobecne w świecie elektroniki i służą reprezentowaniu danych w przystępny (i niedrogi) sposób. Tego typu wyświetlacz może zastąpić droższe ekrany (np. LCD) w przypadku gdy chcemy zobaczyć tylko wartości liczbowe.

Anoda czy katoda?

Najprostsze układy wyświetlaczy siedmiosegmentowych opierają się na wspólnej katodzie lub wspólnej anodzie, co oznacza (w przypadku wspólnej anody), że z aż 10 pinów dwa środkowe będą otrzymywały napięcie, a 8 pozostałych – uziemienia lub na odwrót (w przypadku wspólnej katody).

My pracowaliśmy na wyświetlaczu ze wspólną anodą. Każdy pin pozwala na przepływ prądu przez inny segment (LED) na obszarze wyświetlania. Segmenty mają swoje oznaczenia od a do g (oraz kropka dziesiętna DP – decimal point).
 

Sterowanie wyświetlaczem przy pomocy arduino

Po podłączeniu 8 pinów wyświetlacza do pinów cyfrowych Arduino oraz doprowadzeniu z Arduino zasilania (dodając po drodze opornik) przyszedł czas na programowanie.

Przed dowolnym programem należało przypisać odpowiednie piny w programie.

int start=6;
int end=13;
int i;

„Start” oznacza pierwszy z użytych pinów Arduino, „end” – ostatni. Zmienna „i” jest pomocniczym licznikiem do tablic.

Na próbę stworzyliśmy prosty program pozwalający zapalić wszystkie segmenty wyświetlacza, co pozwoliło przetestować prawidłowe podłączenie wyświetlacza.

void setup(){
  for(i=start;i<=end;i++){
      pinMode(i,OUTPUT);
      digitalWrite(i,1); //0=LOW, 1=HIGH
  }
}

void loop(){
  for(i=start;i<=end;i++)
      digitalWrite(i,1);
  delay(500);
  for(i=start;i<=end;i++)
      digitalWrite(i,0);
  delay(500);
}

W pętlach for widocznych w bloku funkcji void loop() możemy zobaczyć przypisanie stanów wysokich i niskich każdemu z pinów podłączonych do wyświetlacza. Ze względu na to, że jest to wyświetlacz o wspólnej anodzie, to stan niski powoduje zapalenie się lampek, gdyż następuje wtedy przepływ prądu.

Wyświetlanie cyfr

Następnie rozpoczęliśmy tworzenie programu wyświetlającego konkretne cyfry. Stworzyliśmy prosty program w którym wprowadziliśmy własne funkcje – wyświetlające jedynkę (funkcja void jedynka()) , dwójkę (void dwojka()) oraz „czyszczące” wyświetlacz (funkcja void nic()). Budowanie takich funkcji ma ogromny sens – będą to nasz „cegiełki”, które wielokrotnie można później używać – patrz główna funkcja loop() w Arduino. 

int i;
int ledA =8;
int ledB = 9;
int ledC = 11;
int ledD = 12;
int ledE = 13;
int ledF = 7;
int ledG = 6;
int ledDP = 10;
// Kolejność podpięcia diod a - g,DP to właśnie 8,9,11,12,13,7,6,10
void setup(){
  pinMode(ledA,OUTPUT);
  pinMode(ledB,OUTPUT);
  pinMode(ledC,OUTPUT);
  pinMode(ledD,OUTPUT);
  pinMode(ledE,OUTPUT);
  pinMode(ledF,OUTPUT);
  pinMode(ledG,OUTPUT);
  pinMode(ledDP,OUTPUT);
  digitalWrite(ledDP,1);
  nic();
}

void loop(){
  jedynka();
  delay (500);
  dwojka();
  delay (500);
  dwojka();
  delay (500);
  jedynka();
  delay (500);
  nic();
  delay (500);
}

void nic(){
  digitalWrite(ledA,1);
  digitalWrite(ledB,1);
  digitalWrite(ledC,1);
  digitalWrite(ledD,1);
  digitalWrite(ledE,1);
  digitalWrite(ledF,1);
  digitalWrite(ledG,1);
}//nic

void jedynka(){
  digitalWrite(ledA,1);
  digitalWrite(ledB,0);
  digitalWrite(ledC,0);
  digitalWrite(ledD,1);
  digitalWrite(ledE,1);
  digitalWrite(ledF,1);
  digitalWrite(ledG,1);
}//jedynka

void dwojka(){
  digitalWrite(ledA,0);
  digitalWrite(ledB,0);
  digitalWrite(ledC,1);
  digitalWrite(ledD,0);
  digitalWrite(ledE,0);
  digitalWrite(ledF,1);
  digitalWrite(ledG,0);
}//dwojka

W naszym kodzie, są również widoczne zmienne „ledA”, „ledB” itd (linijki 2 – 9), które były tworzone właśnie w nawiązaniu do nazewnictwa widocznym na rysunkach pokazującym budowę wyświetlacza (widocznym na samym początku wpisu). Warto zauważyć, że opłaca się używać nazw zmiennych mających jakieś konkretne znaczenie – tutaj wybraliśmy nazewnictwo zaczerpnięte z budowy sevseg-a. 

Łatwo zauważyć, że powyższy kod jest bardzo powtarzalny i wizualnie zajmuje sporo miejsca, przez co jest też mniej czytelny i nieprofesjonalny. W/w program jest przykładem programowania strukturalnego, czyli hierarchicznego podziału programu na odpowiednie procedury i bloki kodu. Widoczne są utworzone funkcje „nic()”, „jedynka()” i „dwojka()”. Analogicznie należy tworzyć kolejne funkcje generujące kolejne cyfry i wywoływać je w void loop().

Oczywiście na tym nie poprzestaliśmy.

Program z tablicami

Jak widać każda funkcja kolejno zapala odpowiednie segmenty wyświetlacza. Jest to robione „ręcznie”, tj za każdym razem musieliśmy przypisywać każdemu pinowi stan wysoki i niski. Znacznie bardziej optymalną metodą było stworzenie dwuwymiarowej tablicy. Każdy 8-elementowy wiersz tablicy odpowiadał przypisaniu odpowiedniego stanu logicznego danemu segmentowi (jest ich osiem, gdyż poza 7 segmentami jest również 'kropka dziesiętna’ – decimal point). Ten proces pozwolił nam porzucić metodę nazywania każdej diody po kolei (tj. „ledA”, „ledB”…). Na potrzeby tej metody należało dodać tablicę kontrolną z numerami pinów do których podłączone są piny wyświetlacza (linijka 2).

int i;
int led[8]={8,9,11,12,13,7,6,10};
int digits[5][8]={ //na razie definiuje tylko 5 cyfr
// A B C D E F G DP
  {0,0,0,0,0,0,1,0}, //zero
  {1,0,0,1,1,1,1,0}, //wyswietla 1
  {0,0,1,0,0,1,0,0}, //wyswietla 2
  {0,0,0,0,1,1,0,0}, //wyswietla 3
  {1,0,0,1,1,0,0,0}
  }; //...
   //macierz, 4 tablice, kazda z osmioma elementami

void loop(){
  jedynka();
  delay (500);
  dwojka();
  delay (500);
  trojka();
  delay (500);
  czworka();
  delay (500);
  nic();
  delay (500);
}

  void nic(){
    for(i=0;i<8;i++)
      digitalWrite(led[i],digits[0][i]);
  }//nic

  void jedynka(){
    for(i=0;i<8;i++)
      digitalWrite(led[i],digits[1][i]);
  }//jedynka
  
  void dwojka(){
    for(i=0;i<8;i++)
      digitalWrite(led[i],digits[2][i]);
  }//dwojka

Teraz pozostało jedynie przewidzieć który segment ma dostać stan wysoki albo niski, by całość reprezentowała daną liczbę. Kolejne liczby były reprezentowane przez kolejne wiersze w tablicy dwuwymiarowej.

By zrozumieć działanie tej tablicy należy przyjrzeć się działaniu funkcji wywołujących kolejne liczby. Dla przykładu: „jedynka()” zamiast odwoływać się bezpośrednio do każdego pinu przez zmienne ledA, ledB, ledC… (poprzednio), zastąpiliśmy to teraz pętlą „for” przypisującą kolejne stany logiczne (zera lub jedynki) z wiersza tablicy. Każda kolumna tej tablicy odpowiada kolejnej diodzie w wyświetlaczu, czyli w pierwsza kolumna to (poprzednio) ledA (0=świeci, 1=nie świeci), druga to (poprzednio) ledB (0=świeci, 1=nie swieci) itd.

Jeśli wywołujemy jedynkę, pętla „bierze” drugi wiersz w tablicy „digits[]” (uwaga: indeksowanie od zera, więc indeks wiersza=1) i przypisuje diodzie „A” stan wysoki (obszar A nie zaświeci się), diodzie „B” stan niski (dioda zaświeci się). Rysowanie cyfry dwa to branie wiersza numer trzy (indeks wiersza tablicy=2) itd.

Ta operacja zarówno skraca kod, jak i zwiększa jego czytelność i wygodę dodawania kolejnych wyświetlanych liczb lub znaków.

Program wciąż można było ulepszyć.

Ostateczna wersja programu

Optymalizacja kodu dotyczy wywoływania liczb – mamy wiele podobnych funkcji (jedynka(), dwojka() itd). Zamiast tworzyć kolejne funkcje stworzyliśmy jedną funkcję void cyfra(int n), które wywoływały z tablicy dwuwymiarowej kolejne wiersze, czyli kolejne cyfry.

int i;
int led[8]={8,9,11,12,13,7,6,10};
int digits[5][8]={
// A B C D E F G DP
  {0,0,0,0,0,0,1,0}, //zero
  {1,0,0,1,1,1,1,0}, //swieci 1
  {0,0,1,0,0,1,0,0}, //swieci 2
  {0,0,0,0,1,1,0,0}, //swieci 3
  {1,0,0,1,1,0,0,0}
  }; //...
   //macierz, 4 tablice, kazda z osmioma elementami

void setup(){
    for(i=0;i<8;i++)
      pinMode(led[i],OUTPUT);
}

void cyfra(int nr){
  for(i=0;i<8;i++)
    digitalWrite(led[i],digits[nr][i]);
}

void loop(){
  cyfra(1);
  delay (500);
  cyfra(2);
  delay (500);
  cyfra(3);
  delay (500);
  cyfra(4);
  delay (500);
 }

Dodawanie kolejnych liczb stało się niezwykle proste i przejrzyste. Program sprawia wrażenie bardziej profesjonalnego i czytelnego. Uproszczone jest także wyświetlanie. Wystarczy rozszerzyć tablicę o kolejny wiersz i dopisać odpowiednie stany logiczne kolejnym segmentom wyświetlacza.

Podsumowanie

W tym tygodniu poznaliśmy czym jest wyświetlacz 7-segmentowy i w jaki sposób funkcjonuje, odkryliśmy kolejne zastosowania tablic oraz że tablica może być również macierzą, tj. mieć więcej niż jeden wymiar. Ostatecznie zastosowaliśmy metody programowania strukturalnego, które jest obok programowania obiektowego, jedną z najszerzej stosowanych metod w świecie programowania.

Praca domowa

Wykorzystać powyższy kod (np. w wirtualnym arduino) i rozbudować o wczytywanie cyfr z klawiatury (komunikacja szeregowa), czym bawiliśmy się poprzednio.

Do zobaczenia w przyszłym tygodniu!
(c) Maciej 2017