[045] Prawie wszystko o bargrafie

[045] Prawie wszystko o bargrafie

Wszyscy znamy wskazówkowe mierniki i lubimy je. Problem w tym, że trochę nie ma dla nich miejsca we współczesnym świecie, a tymczasem sposób, w jaki wskazywały wartości, miał jedną, wielką zaletę: był dla nas, ludzi szybciej przyswajalny od reprezentacji cyfrowej, czasem „zbyt dokładnej” na pierwszy rzut oka. Dlatego porządne multimetry, oprócz wielocyfrowego wskaźnika mają tak zwane bargrafy. Są to grafiki udające podziałki mierników, które zmieniają swoją długość na wzór takich analogowych mierników wskazówkowych. Żeby to działało dobrze, trzeba wiele elementów składowych i odpowiedniej szybkości pracy. Toteż multimetry zwykle oferują większą częstotliwość odświeżania bargrafu wobec cyfr i wykorzystują do wizualizacji przynajmniej kilkadziesiąt elementów.


Czy jesteśmy w stanie zbudować coś takiego wykorzystując wyświetlacz alfanumeryczny zgodny z HD44780? Jak najbardziej. Na początek rozwiązanie proste, choć ubogie. Wykorzystamy dolny rząd wyświetlacza z poprzedniego artykułu, który dotąd leżał odłogiem. Mamy tam szesnaście pól, więc będziemy wyświetlać taki uproszczony bargraf, składający się z szesnastu elementów. Tworzyć mogą go dowolne znaczki, które wyświetlacz oferuje, ja wybrałem tak zwaną „cegiełkę” czyli po prostu grafikę zapalającą wszystkie piksele pola. Problem w tym, że nie ma ona swojego reprezentanta na klawiaturze i trzeba się do niej dostać za pomocą adresu ASCII. Ale o tym zaraz.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

const byte woltomierz = A1;  // Port, który będzie mierzyć napięcie.
int napiecie;                // Wartość tego napięcia bez jednostek.
float napiecieV;             // Wartość tego napięcia w woltach.
byte bargraf;                // Wartość tego napięcia dla bargrafu.

void setup() {
  lcd.begin(16, 2);  // Inicjalizacja wyświetlacza LCD
}

void loop() {
  lcd.setCursor(0, 0);                // Ustaw kursor na początku wyświetlacza.
  napiecie = analogRead(woltomierz);  // Pobierz wartość napięcia z portu woltomierza.
  lcd.print(napiecie);                // Wyświetl ją.
  lcd.print(F("    "));               // Wyświetl cztery spacje.

  lcd.setCursor(6, 0);       // Ustaw kursor na pzycji szóstej.
  lcd.print(napiecie, HEX);  // Wyświetl zmierzoną wartość w formacie HEX
  lcd.print(F("   "));       // Wyświetl trzy spacje.

  lcd.setCursor(11, 0);                   // Ustaw kursor na pzycji jedenastej.
  napiecieV = napiecie * (5.0 / 1024.0);  // Przelicz dziesięciobitową wartość na wolty.
  lcd.print(napiecieV, 3);                // Wyświetl napięcie z dokładnością do trzech cyfr po przecinku.

  lcd.setCursor(0, 1);                   // Ustaw kursor na początku dolnego wiersza.
  bargraf = napiecie / 64;               // Oblicz długość bargrafu.
  for (byte x = 0; x <= bargraf; x++) {  // Powtarzaj zgodnie z długością bargrafu.
    lcd.write(219);                      // Wyświetl wybraną grafikę.
  }
  lcd.print(F("               "));  // Wyświetl 15 spacji.
}

Zadeklarujmy kolejną zmienną, o nazwie bargraf. Dopiszemy sobie już czwarty blok w głównej pętli. Najpierw przeniesiemy się do dolnego wiersza lcd.setCursor(0, 1) Następnie określimy długość bargrafu bargraf = napiecie / 64 dzieląc napięcie przez 64, bo skoro ilość pozycji wynosi 16, każda kolejna włączy się po przyroście napięcia o 64 jednostki. Kolejno stworzymy pętlę, która wykona się tyle razy, ile wynosi wartość bargrafu. Za każdym razem będziemy wstawiać kolejny znak, ale jak mówiłem – tym razem wysyłając kod tego znaku lcd.write(255) Nie używa się do tego instrukcji print, a write. „Cegiełka” ma kod równy 255, więc taką wartość tutaj wstawiamy. Potem jeszcze trzeba wysłać piętnaście spacji, które w najgorszym przypadku – gdy bargraf skurczy się do jednej cegiełki, wyczyści pozostałe piętnaście pozycji. Po kompilacji dostaniemy coś takiego.

Jak widać, mamy tutaj troszkę nietypową ideę: pierwsza cegiełka świeci się zawsze, także przy zerze, a ostatnia pojawia się w ostatniej 1/16 zakresu. Brak świecenia przy zerze wygląda źle, podobnie jak wypełnienie całości dopiero po osiągnięciu ostatniej dostępnej wartości, czyli u nas 1023. Zmieniając adres grafiki na 219 otrzymamy coś takiego.

Działa to ładnie, lecz 16 pozycji to trochę mało, a poza tym jesteśmy uwiązani do grafik zaszytych w wyświetlaczu. Czas na coś nowego.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

const byte woltomierz = A1;  // Port, który będzie mierzyć napięcie.
int napiecie;                // Wartość tego napięcia bez jednostek.
float napiecieV;             // Wartość tego napięcia w woltach.
byte bargraf;                // Wartość tego napięcia dla bargrafu.

byte znak[8] = { 0b11011, 0b11011, 0b11011, 0b11011, 0b11011, 0b11011, 0b11011, 0b11011 };  // Grafika użytkownika.

void setup() {
  lcd.begin(16, 2);         // Inicjalizacja wyświetlacza LCD
  lcd.createChar(0, znak);  // Zaprogramuj grafikę użytkownika.
}

void loop() {
  lcd.setCursor(0, 0);                // Ustaw kursor na początku wyświetlacza.
  napiecie = analogRead(woltomierz);  // Pobierz wartość napięcia z portu woltomierza.
  lcd.print(napiecie);                // Wyświetl ją.
  lcd.print(F("    "));               // Wyświetl cztery spacje.

  lcd.setCursor(6, 0);       // Ustaw kursor na pzycji szóstej.
  lcd.print(napiecie, HEX);  // Wyświetl zmierzoną wartość w formacie HEX
  lcd.print(F("   "));       // Wyświetl trzy spacje.

  lcd.setCursor(11, 0);                   // Ustaw kursor na pzycji jedenastej.
  napiecieV = napiecie * (5.0 / 1024.0);  // Przelicz dziesięciobitową wartość na wolty.
  lcd.print(napiecieV, 3);                // Wyświetl napięcie z dokładnością do trzech cyfr po przecinku.

  lcd.setCursor(0, 1);                   // Ustaw kursor na początku dolnego wiersza.
  bargraf = napiecie / 64;               // Oblicz długość bargrafu.
  for (byte x = 0; x <= bargraf; x++) {  // Powtarzaj zgodnie z długością bargrafu.
    lcd.write(0);                        // Wyświetl wybraną grafikę.
  }
  lcd.print(F("               "));  // Wyświetl 15 spacji.
}

Zaczniemy od poprawienia tego, co już jest. W tym celu skorzystamy z możliwości dotąd przeze mnie niewykorzystywanej. Każdy wyświetlacz zgodny z HD44780 może przechowywać do ośmiu definiowanych grafik, wykorzystujących matrycę pięć na osiem pikseli. Aby z tego skorzystać, należy wprowadzić do wyświetlacza mapę takiej grafiki. Stworzymy sobie zatem tablicę znak składającą się z ośmiu bajtów – bo tyle mamy wierszy w znaku. Każdy bajt będzie reprezentował pięć pikseli. Dlatego najlepiej zapisywać je w postaci binarnej. Nie musimy definiować całego bajtu, trzy najstarsze piksele nie mają znaczenia. Grafika, którą sobie wymyśliłem, ma formę „płotka”, czyli mają się świecić wszystkie kolumny oprócz środkowej. Znaczy to, że w środku pozostawimy zera – nie będą się świecić, a po obu stronach będą jedynki.

Następnie zaprogramujemy sobie grafikę stosownym rozkazem biblioteki obsługującej wyświetlacz: lcd.createChar(0, znak) Znaki użytkownika mają adresy ASCII od zera do siedem (a także bliźniaczo: od 8 do 15), więc logicznym będzie użyć najniższego. Poza tym nic już nie zmieniamy poza zmianą adresu znaku, gdzie już eksperymentowaliśmy. Bargraf wygląda teraz tak.

No to skoro zakosztowaliśmy zabaw grafiką, narysujmy coś. Stworzymy ramkę, w której będzie sobie teraz siedział bargraf. Ramka pozwoli ocenić proporcję części wyświetlonej do ukrytej, a do tego będzie ładnie wyglądać.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

const byte woltomierz = A1;  // Port, który będzie mierzyć napięcie.
int napiecie;                // Wartość tego napięcia bez jednostek.
float napiecieV;             // Wartość tego napięcia w woltach.
byte bargraf;                // Wartość tego napięcia dla bargrafu.

byte znak0[8] = { 0b11011, 0b00000, 0b11011, 0b11011, 0b11011, 0b11011, 0b00000, 0b11011 };  // Grafiki użytkownika.
byte znak1[8] = { 0b11011, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11011 };
byte znak2[8] = { 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001 };
byte znak3[8] = { 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000 };

void setup() {
  lcd.begin(16, 2);          // Inicjalizacja wyświetlacza LCD
  lcd.createChar(0, znak0);  // Zaprogramuj grafiki użytkownika.
  lcd.createChar(1, znak1);
  lcd.createChar(2, znak2);
  lcd.createChar(3, znak3);
}

void loop() {
  lcd.setCursor(0, 0);                // Ustaw kursor na początku wyświetlacza.
  napiecie = analogRead(woltomierz);  // Pobierz wartość napięcia z portu woltomierza.
  lcd.print(napiecie);                // Wyświetl ją.
  lcd.print(F("    "));               // Wyświetl cztery spacje.

  lcd.setCursor(6, 0);       // Ustaw kursor na pzycji szóstej.
  lcd.print(napiecie, HEX);  // Wyświetl zmierzoną wartość w formacie HEX
  lcd.print(F("   "));       // Wyświetl trzy spacje.

  lcd.setCursor(11, 0);                   // Ustaw kursor na pzycji jedenastej.
  napiecieV = napiecie * (5.0 / 1024.0);  // Przelicz dziesięciobitową wartość na wolty.
  lcd.print(napiecieV, 3);                // Wyświetl napięcie z dokładnością do trzech cyfr po przecinku.

  lcd.setCursor(0, 1);             // Ustaw kursor na początku dolnego wiersza.
  lcd.write(2);                    // Wyświetl lewą ramkę.
  for (byte x = 0; x < 14; x++) {  // Powtarzaj 14 razy.
    lcd.write(1);                  // Wyświetl spację z górnymi ramkami.
  }
  lcd.write(3);                          // Wyświetl prawą ramkę.
  lcd.setCursor(1, 1);                   // Ustaw kursor na drugiej pozycji dolnego wiersza.
  bargraf = (napiecie + 73) / 74;        // Oblicz długość bargrafu.
  for (byte x = 1; x <= bargraf; x++) {  // Powtarzaj zgodnie z długością bargrafu.
    lcd.write(0);                        // Wyświetl wybraną grafikę.
  }
}

Posłużymy się tutaj pewnym trikiem. Otóż najpierw narysujemy pustą ramkę, a następnie wypełnimy ją zapalonymi cegiełkami, ale tym razem nie tak wysokimi, a czteropikselowymi tylko. Rzędy pikseli na górze i na dole będą rysować ramkę w miejscu oryginalnej. Natomiast za cegiełkami nie będziemy już rysować spacji – bo by nam ona popsuła ramkę, tylko zostawimy wcześniej narysowane spreparowane spacje z poziomą częścią ramki. Trzeba pamiętać, że narysowanie czegokolwiek w obrębie znaku niszczy wszystko, co było tam narysowane wcześniej. Potrzebujemy zatem następujących elementów: cegiełek z kawałkiem poziomych części ramki, spację z tymiż, lewy początek ramki i prawy koniec.

Zaczynamy od narysowania lewego początku ramki lcd.write(2) potem czternastu naszych ramkowych spacji for (byte x = 0; x < 14; x++) {lcd.write(1);} i prawego końca lcd.write(3) Jak już nam się to pojawi, należy wypełnić ramkę spreparowanymi sprytnie cegiełkami. Ale nie jak było, przez całą szerokość wyświetlacza, a w obrębie czternastu elementów znajdujących się wewnątrz. Inaczej popsulibyśmy ramkę. Trzeba więc przeliczyć zakres z 16 do 14 pozycji bargraf = (napiecie + 73) / 74 Poza tym – ponieważ mamy tutaj ramkę – postanowiłem dla zera w ogóle nie malować bargrafu. W tym celu przesunąłem zakres o 73, czyli wartość całego przyrostu bargrafu minus jeden. Od tej pory dla zera nie będzie żadnej cegiełki, a potem będą sobie one wskakiwać proporcjonalnie aż do wartości maksymalnej.

Wygląda to fajnie, tylko jakoś czasem trochę mruga. I ma mrugać, bo wyświetlacz rysujemy w pętli bez końca, z maksymalną szybkością. Nie ma takiej potrzeby, wystarczy go rysować tylko wtedy, gdy wartość mierzona się zmieni. Wprowadźmy zatem jeszcze jedną modyfikację.

#include <Wire.h>                                                      // Biblioteka obsługująca magistralę I2C
#include <hd44780.h>                                                   // Biblioteka obsługująca wyświetlacze 44780
#include <hd44780ioClass/hd44780_I2Cexp.h>                             // Dodatek obsługujący wyświetlacze podłączone do ekspandera I2C
hd44780_I2Cexp lcd(0x20, I2Cexp_MCP23008, 7, 6, 5, 4, 3, 2, 1, HIGH);  // Konfiguracja połączeń wyświetlacza LCD

const byte woltomierz = A1;  // Port, który będzie mierzyć napięcie.
int napiecie;                // Wartość tego napięcia bez jednostek.
float napiecieV;             // Wartość tego napięcia w woltach.
byte bargraf;                // Wartość tego napięcia dla bargrafu.
int napiecieStare = 255;     // Wartość poprzednio zmierzonego napięcia.

byte znak0[8] = { 0b11011, 0b00000, 0b11011, 0b11011, 0b11011, 0b11011, 0b00000, 0b11011 };  // Grafiki użytkownika.
byte znak1[8] = { 0b11011, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11011 };
byte znak2[8] = { 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001, 0b00001 };
byte znak3[8] = { 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000 };

void setup() {
  lcd.begin(16, 2);          // Inicjalizacja wyświetlacza LCD
  lcd.createChar(0, znak0);  // Zaprogramuj grafiki użytkownika.
  lcd.createChar(1, znak1);
  lcd.createChar(2, znak2);
  lcd.createChar(3, znak3);
}

void loop() {
  while (abs(napiecie - napiecieStare) < 4) {  // Porównaj ze sobą kolejne mierzone wartości napięć i wyjdź, jeśli różnią się o więcej niż 3
    napiecie = analogRead(woltomierz);         // Pobierz wartość napięcia z portu woltomierza.
  }
  napiecieStare = napiecie;  // Przepisz wartość zmierzonego napięcia do zmiennej napiecieStare.

  lcd.setCursor(0, 0);   // Ustaw kursor na początku wyświetlacza.
  lcd.print(napiecie);   // Wyświetl ją.
  lcd.print(F("    "));  // Wyświetl cztery spacje.

  lcd.setCursor(6, 0);       // Ustaw kursor na pzycji szóstej.
  lcd.print(napiecie, HEX);  // Wyświetl zmierzoną wartość w formacie HEX
  lcd.print(F("   "));       // Wyświetl trzy spacje.

  lcd.setCursor(11, 0);                   // Ustaw kursor na pzycji jedenastej.
  napiecieV = napiecie * (5.0 / 1024.0);  // Przelicz dziesięciobitową wartość na wolty.
  lcd.print(napiecieV, 3);                // Wyświetl napięcie z dokładnością do trzech cyfr po przecinku.

  lcd.setCursor(0, 1);             // Ustaw kursor na początku dolnego wiersza.
  lcd.write(2);                    // Wyświetl lewą ramkę.
  for (byte x = 0; x < 14; x++) {  // Powtarzaj 14 razy.
    lcd.write(1);                  // Wyświetl spację z górnymi ramkami.
  }
  lcd.write(3);                          // Wyświetl prawą ramkę.
  lcd.setCursor(1, 1);                   // Ustaw kursor na drugiej pozycji dolnego wiersza.
  bargraf = (napiecie + 73) / 74;        // Oblicz długość bargrafu.
  for (byte x = 1; x <= bargraf; x++) {  // Powtarzaj zgodnie z długością bargrafu.
    lcd.write(0);                        // Wyświetl wybraną grafikę.
  }
}

Powołamy do życia jeszcze jedną zmienną o nazwie napiecieStare. Na początku głównej pętli odejmiemy od siebie wartości napięć while (abs(napiecie - napiecieStare) < 4) Za pierwszym razem wynik będzie równy 255, bo tak zdefiniowałem wstępnie tę zmienną, by po resecie zacząć od narysowania grafik. Jeśli wartość bezwzględna tej operacji będzie mniejsza od czterech, będziemy mierzyć napięcie napiecie = analogRead(woltomierz) i porównywać ze zmienną napiecieStare. W końcu kiedyś wartość mierzonego napięcia zmieni się na tyle, by warunek nie zaszedł i będziemy mogli z tej pętli wyjść. Pierwszą rzeczą, którą zrobimy, będzie aktualizacja wartości napiecieStare co dopiero zmierzoną wielkością. Potem narysujemy wszystko co trzeba i ponownie wejdziemy w pętlę, oczekując kolejnego uaktualnienia napięcia. Parametr przy wartości bezwzględnej możemy dobierać zgodnie z potrzebami: im większy, tym większa odporność na miganie znaków, ale też mniejsza dokładność przy delikatnym poruszaniu potencjometrem.

I to byłoby na tyle. Wciąż nie zwiększyliśmy rozdzielczości bargrafu, a nawet zmniejszyliśmy ją do 14 pozycji. O tym jak sobie z tym poradzić napiszę w kolejnym artykule.

Powiązane tematy

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2Sprawdź tutaj

Przeczytaj również

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2024 arduino.pl