[047] Wyświetlacz HD44780 i duże cyfry

[047] Wyświetlacz HD44780 i duże cyfry

Wyświetlaczy wykorzystujących HD44780 powstało zapewne kilka miliardów i gdyby Hitachi, projektując je roku 80 zdawał sobie sprawę z ich przyszłej popularności, z pewnością wcisnąłby doń więcej pamięci znaków użytkownika niż marne osiem. Cóż, dobre i to. Jak widzieliśmy w jednym z artykułów, jeśli ruszy się głową, ta skromna ilość pozwala osadzić grafiki czy na przykład polskie litery. Jednak ośmioma polami nie zaspokoimy ambicji stworzenia choćby własnych krojów cyfr, nie mówiąc o alfabecie. Czy aby na pewno?


Aby dostać więcej za nic, trzeba do problemu podejść, że tak powiem od tyłu. Zamiast szukać miejsca na więcej znaków definiowalnych – którego i tak nie znajdziemy – niech narzędzie do definiowania grafik stanie się naszym narzędziem rysującym. Pod jednym wszakże warunkiem: metoda zadziała, jeśli wykorzystamy tylko osiem pól z całej powierzchni wyświetlacza. Na czym polega sposób? Otóż bez względu na treść, zawsze będziemy rysować te same znaki ASCII: od 0 do 7. Treść będziemy osadzać, definiując na nowo obszar pamięci, w którym wyświetlacz przechowuje wzory znaków, czyli używając poznanego już polecenia lcd.createChar. Bierzmy się do roboty.

#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.
int napiecieStare = 255;     // Wartość poprzednio zmierzonego napięcia.

byte znak[160] = { 0b00111, 0b01111, 0b01110, 0b01110, 0b01110, 0b01110, 0b01111, 0b00111,  // Serif
                   0b11100, 0b11110, 0b01110, 0b01110, 0b01110, 0b01110, 0b11110, 0b11100,
                   0b00000, 0b00001, 0b00011, 0b00111, 0b00000, 0b00000, 0b00000, 0b00011,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11110,
                   0b00111, 0b01111, 0b01110, 0b00000, 0b00000, 0b00011, 0b00111, 0b01111,
                   0b11100, 0b11110, 0b01110, 0b01110, 0b11100, 0b11000, 0b11110, 0b11110,
                   0b00111, 0b01111, 0b01100, 0b00001, 0b00000, 0b01100, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01110, 0b11100, 0b01110, 0b01110, 0b11110, 0b11100,
                   0b00000, 0b00000, 0b00001, 0b00011, 0b00110, 0b01111, 0b01111, 0b00000,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11110, 0b11110, 0b11100,
                   0b01111, 0b01111, 0b01110, 0b01111, 0b00000, 0b01110, 0b01111, 0b00111,
                   0b11100, 0b11100, 0b00000, 0b11100, 0b01110, 0b01110, 0b11110, 0b11100,
                   0b00111, 0b01111, 0b01110, 0b01111, 0b01110, 0b01110, 0b01111, 0b00111,
                   0b11100, 0b11100, 0b00000, 0b11100, 0b01110, 0b01110, 0b11110, 0b11100,
                   0b01111, 0b01111, 0b01000, 0b00000, 0b00001, 0b00011, 0b00111, 0b01111,
                   0b11110, 0b11110, 0b01110, 0b11100, 0b11000, 0b10000, 0b00000, 0b00000,
                   0b00111, 0b01111, 0b01110, 0b00111, 0b01110, 0b01110, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01110, 0b11100, 0b01110, 0b01110, 0b11110, 0b11100,
                   0b00111, 0b01111, 0b01110, 0b01110, 0b00111, 0b00000, 0b00111, 0b00111,
                   0b11100, 0b11110, 0b01110, 0b01110, 0b11110, 0b01110, 0b11110, 0b11100 };
/*
byte znak[160] = { 0b00111, 0b01111, 0b01110, 0b01110, 0b01110, 0b01110, 0b01111, 0b00111,  // Sans serif
                   0b11100, 0b11110, 0b01110, 0b01110, 0b01110, 0b01110, 0b11110, 0b11100,
                   0b00000, 0b00001, 0b00011, 0b00111, 0b00000, 0b00000, 0b00000, 0b00000,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,
                   0b00111, 0b01111, 0b01110, 0b00000, 0b00001, 0b00011, 0b00111, 0b01111,
                   0b11100, 0b11110, 0b01110, 0b11100, 0b11000, 0b10000, 0b11110, 0b11110,
                   0b00111, 0b01111, 0b01110, 0b00000, 0b00000, 0b01110, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01110, 0b11100, 0b11110, 0b01110, 0b11110, 0b11100,
                   0b00000, 0b00000, 0b00001, 0b00011, 0b00110, 0b01111, 0b01111, 0b00000,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11110, 0b11110, 0b11100,
                   0b01111, 0b01111, 0b01110, 0b01111, 0b01111, 0b00000, 0b01111, 0b01111,
                   0b11100, 0b11100, 0b00000, 0b11100, 0b11110, 0b01110, 0b11110, 0b11100,
                   0b00111, 0b01111, 0b01110, 0b01111, 0b01111, 0b01110, 0b01111, 0b00111,
                   0b11100, 0b11100, 0b00000, 0b11100, 0b11110, 0b01110, 0b11110, 0b11100,
                   0b01111, 0b01111, 0b00000, 0b00000, 0b00001, 0b00011, 0b00111, 0b01110,
                   0b11110, 0b11110, 0b01110, 0b11100, 0b11000, 0b10000, 0b00000, 0b00000,
                   0b00111, 0b01111, 0b01110, 0b00111, 0b01111, 0b01110, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01110, 0b11100, 0b11110, 0b01110, 0b11110, 0b11100,
                   0b00111, 0b01111, 0b01110, 0b01111, 0b00111, 0b00000, 0b00111, 0b00111,
                   0b11100, 0b11110, 0b01110, 0b11110, 0b11110, 0b01110, 0b11110, 0b11100 };

byte znak[160] = { 0b01111, 0b01111, 0b01110, 0b01110, 0b01110, 0b01110, 0b01111, 0b01111,  // Block
                   0b11110, 0b11110, 0b01110, 0b01110, 0b01110, 0b01110, 0b11110, 0b11110,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000,
                   0b11110, 0b11110, 0b01110, 0b01110, 0b01110, 0b01110, 0b01110, 0b01110,
                   0b01111, 0b01111, 0b00000, 0b01111, 0b01111, 0b01110, 0b01111, 0b01111,
                   0b11110, 0b11110, 0b01110, 0b11110, 0b11110, 0b00000, 0b11110, 0b11110,
                   0b01111, 0b01111, 0b00000, 0b00011, 0b00011, 0b00000, 0b01111, 0b01111,
                   0b11110, 0b11110, 0b01110, 0b11110, 0b11110, 0b01110, 0b11110, 0b11110,
                   0b01110, 0b01110, 0b01110, 0b01111, 0b01111, 0b00000, 0b00000, 0b00000,
                   0b01110, 0b01110, 0b01110, 0b11110, 0b11110, 0b01110, 0b01110, 0b01110,
                   0b01111, 0b01111, 0b01110, 0b01111, 0b01111, 0b00000, 0b01111, 0b01111,
                   0b11110, 0b11110, 0b00000, 0b11110, 0b11110, 0b01110, 0b11110, 0b11110,
                   0b01111, 0b01111, 0b01110, 0b01111, 0b01111, 0b01110, 0b01111, 0b01111,
                   0b11110, 0b11110, 0b00000, 0b11110, 0b11110, 0b01110, 0b11110, 0b11110,
                   0b01111, 0b01111, 0b01110, 0b01110, 0b00000, 0b00000, 0b00000, 0b00000,
                   0b11110, 0b11110, 0b01110, 0b01110, 0b01110, 0b01110, 0b01110, 0b01110,
                   0b01111, 0b01111, 0b01110, 0b01111, 0b01111, 0b01110, 0b01111, 0b01111,
                   0b11110, 0b11110, 0b01110, 0b11110, 0b11110, 0b01110, 0b11110, 0b11110,
                   0b01111, 0b01111, 0b01110, 0b01111, 0b01111, 0b00000, 0b01111, 0b01111,
                   0b11110, 0b11110, 0b01110, 0b11110, 0b11110, 0b01110, 0b11110, 0b11110 };

byte znak[160] = { 0b00111, 0b01111, 0b01110, 0b01110, 0b01110, 0b01111, 0b00111, 0b00000,  // Serif, 7 wierszy.
                   0b11100, 0b11110, 0b01110, 0b01110, 0b01110, 0b11110, 0b11100, 0b00000,
                   0b00000, 0b00001, 0b00011, 0b00111, 0b00000, 0b00000, 0b00000, 0b00000,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b00000,
                   0b00111, 0b01111, 0b01100, 0b00001, 0b00011, 0b00111, 0b01111, 0b00000,
                   0b11100, 0b11110, 0b01110, 0b11100, 0b11000, 0b11110, 0b11110, 0b00000,
                   0b00111, 0b01111, 0b01100, 0b00001, 0b01100, 0b01111, 0b00111, 0b00000,
                   0b11100, 0b11110, 0b01110, 0b11100, 0b01110, 0b11110, 0b11100, 0b00000,
                   0b00000, 0b00000, 0b00001, 0b00011, 0b00110, 0b01111, 0b00000, 0b00000,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11110, 0b11100, 0b00000,
                   0b01111, 0b01110, 0b01111, 0b00000, 0b01110, 0b01111, 0b00111, 0b00000,
                   0b11100, 0b00000, 0b11100, 0b01110, 0b01110, 0b11110, 0b11100, 0b00000,
                   0b00111, 0b01110, 0b01111, 0b01110, 0b01110, 0b01111, 0b00111, 0b00000,
                   0b11100, 0b00000, 0b11100, 0b01110, 0b01110, 0b11110, 0b11100, 0b00000,
                   0b01111, 0b01111, 0b01000, 0b00000, 0b00001, 0b00011, 0b00011, 0b00000,
                   0b11110, 0b11110, 0b01110, 0b11100, 0b11000, 0b10000, 0b10000, 0b00000,
                   0b00111, 0b01111, 0b01110, 0b00111, 0b01110, 0b01111, 0b00111, 0b00000,
                   0b11100, 0b11110, 0b01110, 0b11100, 0b01110, 0b11110, 0b11100, 0b00000,
                   0b00111, 0b01111, 0b01110, 0b01110, 0b00111, 0b00000, 0b00111, 0b00000,
                   0b11100, 0b11110, 0b01110, 0b01110, 0b11110, 0b01110, 0b11100, 0b00000 };
*/
void setup() {
  lcd.begin(16, 2);  // Inicjalizacja wyświetlacza LCD
}

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.createChar(0, znak + 16 * ((napiecie / 1000) % 10));      // Lewa część tysięcy.
  lcd.createChar(1, znak + 16 * ((napiecie / 1000) % 10) + 8);  // Prawa część tysięcy.
  lcd.createChar(2, znak + 16 * ((napiecie / 100) % 10));       // Lewa część setek.
  lcd.createChar(3, znak + 16 * ((napiecie / 100) % 10) + 8);   // Prawa część setek.
  lcd.createChar(4, znak + 16 * ((napiecie / 10) % 10));        // Lewa część dziesiątek.
  lcd.createChar(5, znak + 16 * ((napiecie / 10) % 10) + 8);    // Prawa część dziesiątek.
  lcd.createChar(6, znak + 16 * (napiecie % 10));               // Lewa część jednostek.
  lcd.createChar(7, znak + 16 * (napiecie % 10) + 8);           // Prawa część jednostek.

  lcd.setCursor(4, 0);  // Ustaw kursor na pzycji czwartej.
  lcd.write(0);         // Wyślij kolejno adresy wszystkich grafik użytkownika.
  lcd.write(1);
  lcd.write(2);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
  lcd.write(6);
  lcd.write(7);
}

Dostąpiłem lekkiego wzruszenia, odkopując źródła asemblerowych programów na ‘51 sprzed prawie 30 lat. Otóż na pomysł, który zaraz przedstawię, wpadłem wówczas właśnie, mając dość typowych, może i czytelnych, ale już wtedy wyeksploatowanych fontów. Bardzo chciałem mieć cyferki o rozmiarze 10x8 pikseli, dużo większe i grubsze od standardowych. Źródła te przydały mi są teraz po to, by skopiować sobie cztery kroje cyfr, z mozołem niegdyś projektowane. Nie przypuszczałem wówczas, że posłużą mi jeszcze w jakimś tajemniczym Arduino.

Wygląda to tak: w tablicy znak mamy aż 160 bajtów, po 16 dla każdej z cyfr. Szesnaście – bo każda cyfra zajmie dwa pola, a nie jedno. Z tego wniosek, że będę mógł wyświetlić tylko cztery takie cyfry jednocześnie, ale to jest akurat w sam raz na zegarek, co prawda bez sekundnika albo do prezentowania wartości mierzonych w zakresie do 9999. W każdym razie bywa to użyteczne.

Zatem po stworzeniu tablicy o nazwie znak przechodzimy do głównej pętli, w której znajdziemy procedurę tworzącą z tych danych czterocyfrową liczbę. Cała tajemnica tkwi w tym, by najpierw zaprogramować hurtem osiem pól grafik, a potem wyświetlić je w wybranym miejscu. Pierwszy blok programuje grafiki, w sposób nieco zawiły, odwołując się do pozycji w tablicy znak. W każdym przypadku, używając wyrażenia wyciągającego ze zmiennej napięcie, reszty z dzieleń przez dziesiątki, setki i tysiące i przesunięć określamy parę adresów ośmiobajtowych paczek grafik reprezentujących kolejne cyfry do wyświetlenia.

Potem już tylko trzeba wysłać znaki ASCII od adresu zerowego do siódmego i liczba o wartości napiecie ukaże się nam w zupełnie nowej szacie.

W źródłach zamieściłem jeszcze trzy zbiory krojów: bezszeryfowe, blokowe oraz kopię widocznego przed chwilą, ale o wysokości siedmiu pikseli. Jest on kompatybilny ze standardowymi krojami wyświetlacza, które mają taki właśnie rozmiar, więc można je ze sobą mieszać. Wzory te są mojego autorstwa i jeśli ktoś będzie ich potrzebował do czegokolwiek – niech sobie używa ile bądź.

Apetyt rośnie w miarę jedzenia. A może by tak iść dalej i stworzyć krój o rozmiarze czterech pól, czyli 10x16 pikseli? W tym przypadku niestety będziemy mogli wyświetlić jedynie dwie cyfry naraz, choć… nie do końca, ale o tym za chwilę. Bywa to nadal użyteczne, na przykład do stworzenia termometru albo wskaźnika programów w odbiorniku radiowym.

#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.

byte znak[320] = { 0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,  // Wzorce dużych cyfr
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b00111, 0b00111,
                   0b00111, 0b00111, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00001, 0b00011, 0b00000, 0b00000,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00011, 0b00011,
                   0b00100, 0b01100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11111, 0b11111,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b01100, 0b00000, 0b00000,
                   0b00111, 0b01110, 0b11100, 0b11100, 0b11100, 0b11111, 0b11111, 0b11111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b01110, 0b11100,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00111, 0b11111, 0b11111, 0b11111,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b01100, 0b00000, 0b00000,
                   0b00000, 0b00000, 0b01100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b01110, 0b11100,
                   0b11100, 0b01110, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00001, 0b00011, 0b00111, 0b01110,
                   0b11100, 0b11111, 0b11111, 0b11111, 0b00000, 0b00000, 0b00011, 0b00011,
                   0b00100, 0b01100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,
                   0b11100, 0b11111, 0b11111, 0b11111, 0b11100, 0b11100, 0b11111, 0b11111,
                   0b11111, 0b11111, 0b11100, 0b11100, 0b11100, 0b11111, 0b11111, 0b11110,
                   0b00000, 0b00000, 0b01100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11111, 0b11111, 0b00111, 0b00000, 0b00000, 0b11100, 0b11110, 0b01111,
                   0b00111, 0b00111, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b11111, 0b11111, 0b11110,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01110, 0b00000, 0b00000, 0b11100, 0b11110, 0b01111,
                   0b00111, 0b00111, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b11111, 0b11111, 0b11111, 0b11100, 0b00000, 0b00000, 0b00000, 0b00000,
                   0b00111, 0b01110, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b01100,
                   0b11111, 0b11111, 0b11111, 0b00111, 0b00111, 0b00111, 0b01110, 0b11100,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b11100, 0b01110, 0b00111,
                   0b01110, 0b11100, 0b11100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b01110, 0b11100,
                   0b01110, 0b00111, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,
                   0b11110, 0b01111, 0b00111, 0b00000, 0b00000, 0b01110, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b00111, 0b00111,
                   0b01111, 0b11111, 0b11111, 0b00111, 0b00111, 0b01111, 0b11110, 0b11100 };

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

void loop() {
  napiecie = map(analogRead(woltomierz), 0, 1023, 0, 99);  // Mapuj wartość napięcia do przedziału (0, 99)

  lcd.createChar(0, znak + 32 * (napiecie / 10));       // Lewa górna część dziesiątek.
  lcd.createChar(1, znak + 32 * (napiecie / 10) + 8);   // Lewa dolna część dziesiątek.
  lcd.createChar(2, znak + 32 * (napiecie / 10) + 16);  // Prawa górna część dziesiątek.
  lcd.createChar(3, znak + 32 * (napiecie / 10) + 24);  // Prawa dolna część dziesiątek.
  lcd.createChar(4, znak + 32 * (napiecie % 10));       // Lewa górna część jednostek.
  lcd.createChar(5, znak + 32 * (napiecie % 10) + 8);   // Lewa dolna część jednostek.
  lcd.createChar(6, znak + 32 * (napiecie % 10) + 16);  // Prawa górna część jednostek.
  lcd.createChar(7, znak + 32 * (napiecie % 10) + 24);  // Prawa dolna część jednostek.

  lcd.setCursor(6, 0);  // Ustaw kursor na pozycji szóstej w wierszu górnym.
  lcd.write(0);         // Wyślij kolejno adresy górnych części grafik.
  lcd.write(2);
  lcd.write(4);
  lcd.write(6);
  lcd.setCursor(6, 1);  // Ustaw kursor na pozycji szóstej w wierszu dolnym.
  lcd.write(1);         // Wyślij kolejno adresy górnych części grafik.
  lcd.write(3);
  lcd.write(5);
  lcd.write(7);
}

Zacznijmy od tego, że teraz wzorce będą zajmować aż 320 bajtów. Skoro każda cyfra wymaga czterech pól, czyli 32 bajtów, dziesięć cyfr potrzebuje takiej wartości. Kolejność składowych jest nieco inna niż bym to zrobił teraz: lewa góra, lewy dół, prawa góra, prawy dół. Takiej potrzebowałem wiele lat temu, a nie chciało mi się tego teraz przestawiać, bo o pomyłkę nietrudno przy tej ilości bajtów. Zamiesza nam to tylko kolejność rysowania grafik.

Na początku musimy zmienić sposób tworzenia zmiennej napięcie, bo dwie cyfry ograniczą nas do zakresu 0-99. Użyłem tutaj funkcji map, świetnie nadającej się do takiego celu. Przypomnę, że przeliczy ona pierwotny zakres od zera do 1023 na nowy: 0 do 99.

Procedura programowania grafik jest podobna, ale różni się przesunięciami adresów. Tym razem występuje tu współczynnik 32, bo tyle bajtów odpowiada za jedną cyfrę no i będziemy korzystać z reszty dzieleń przez 10 i dzielenia przez 10 – wszak nie mamy tu setek i tysięcy, jak poprzednio. Ponieważ adresy kolejnych pól różnią się o 8, a każda cyfra wykorzystuje 4 pola, ładujemy dane z adresów przesuniętych o 8, 16 i 24 względem bazowego.

W końcu gdy już wszystko siedzi w pamięci wyświetlacza, rysujemy nasze wielkie cyfry. Najpierw górne połowy, potem dolne. Jak widać, na górze mamy parzyste adresy ASCII, na dole – nieparzyste. To pokłosie owego układu sprzed 30 lat. Po kompilacji możemy cieszyć się gigantycznymi cyferkami.

Tutaj mamy cyfry, ale oczywiście możemy sobie zaprogramować także litery i jakiekolwiek grafiki, łącząc je w razie potrzeby w pary albo czwórkami. Co najwyżej spędzimy więcej czasu nad programowaniem takich kształtów.

Na koniec – wspominałem coś o możliwości przekroczenia ograniczeń ośmiu grafik użytkownika. Dałoby to nam na przykład osiem cyfr w przypadku podwójnych znaków albo cztery – w ostatnim przykładzie. Spróbujmy zatem, acz od razu powiem, że wyniki będą takie sobie i mocno zależne od konkretnego wyświetlacza. Na czym polega sposób? Nadal będziemy mieć tylko osiem pól własnych grafik, ale nic nie stoi na przeszkodzie, by oszukiwać nasze oczy, wyświetlając raz jedną ósemkę, a po chwili drugą, zamazując obraz pierwszej spacjami. I tak w kółko. Jeśli zrobimy to odpowiednio szybko, będziemy widzieć dwie ósemki naraz. Niestety wyświetlacze LCD są zwykle tak wolne, że albo wszystko się rozmaże, albo będzie migać. Dużo lepiej do tego będą się nadawać wyświetlacze OLED albo VDF.

#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.

byte znak[320] = { 0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,  // Wzorce dużych cyfr
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b00111, 0b00111,
                   0b00111, 0b00111, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00001, 0b00011, 0b00000, 0b00000,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00011, 0b00011,
                   0b00100, 0b01100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11111, 0b11111,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b01100, 0b00000, 0b00000,
                   0b00111, 0b01110, 0b11100, 0b11100, 0b11100, 0b11111, 0b11111, 0b11111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b01110, 0b11100,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00111, 0b11111, 0b11111, 0b11111,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b01100, 0b00000, 0b00000,
                   0b00000, 0b00000, 0b01100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b01110, 0b11100,
                   0b11100, 0b01110, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00001, 0b00011, 0b00111, 0b01110,
                   0b11100, 0b11111, 0b11111, 0b11111, 0b00000, 0b00000, 0b00011, 0b00011,
                   0b00100, 0b01100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,
                   0b11100, 0b11111, 0b11111, 0b11111, 0b11100, 0b11100, 0b11111, 0b11111,
                   0b11111, 0b11111, 0b11100, 0b11100, 0b11100, 0b11111, 0b11111, 0b11110,
                   0b00000, 0b00000, 0b01100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11111, 0b11111, 0b00111, 0b00000, 0b00000, 0b11100, 0b11110, 0b01111,
                   0b00111, 0b00111, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b11111, 0b11111, 0b11110,
                   0b11100, 0b11100, 0b11100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01110, 0b00000, 0b00000, 0b11100, 0b11110, 0b01111,
                   0b00111, 0b00111, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b11111, 0b11111, 0b11111, 0b11100, 0b00000, 0b00000, 0b00000, 0b00000,
                   0b00111, 0b01110, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100, 0b01100,
                   0b11111, 0b11111, 0b11111, 0b00111, 0b00111, 0b00111, 0b01110, 0b11100,
                   0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b11100, 0b01110, 0b00111,
                   0b01110, 0b11100, 0b11100, 0b11100, 0b11110, 0b11111, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b01110, 0b11100,
                   0b01110, 0b00111, 0b00111, 0b00111, 0b01111, 0b11111, 0b11110, 0b11100,
                   0b00111, 0b01111, 0b11110, 0b11100, 0b11100, 0b11100, 0b11100, 0b11100,
                   0b11110, 0b01111, 0b00111, 0b00000, 0b00000, 0b01110, 0b01111, 0b00111,
                   0b11100, 0b11110, 0b01111, 0b00111, 0b00111, 0b00111, 0b00111, 0b00111,
                   0b01111, 0b11111, 0b11111, 0b00111, 0b00111, 0b01111, 0b11110, 0b11100 };

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

void loop() {
  napiecie = analogRead(woltomierz);

  lcd.setCursor(8, 0);  // Wyczyść miejsca, w których tkwią prawe cyfry.
  lcd.print("    ");
  lcd.setCursor(8, 1);
  lcd.print("    ");

  lcd.createChar(0, znak + 32 * ((napiecie / 1000) % 10));       // Lewa górna część dziesiątek.
  lcd.createChar(1, znak + 32 * ((napiecie / 1000) % 10) + 8);   // Lewa dolna część dziesiątek.
  lcd.createChar(2, znak + 32 * ((napiecie / 1000) % 10) + 16);  // Prawa górna część dziesiątek.
  lcd.createChar(3, znak + 32 * ((napiecie / 1000) % 10) + 24);  // Prawa dolna część dziesiątek.
  lcd.createChar(4, znak + 32 * ((napiecie / 100) % 10));        // Lewa górna część jednostek.
  lcd.createChar(5, znak + 32 * ((napiecie / 100) % 10) + 8);    // Lewa dolna część jednostek.
  lcd.createChar(6, znak + 32 * ((napiecie / 100) % 10) + 16);   // Prawa górna część jednostek.
  lcd.createChar(7, znak + 32 * ((napiecie / 100) % 10) + 24);   // Prawa dolna część jednostek.

  lcd.setCursor(4, 0);  // Ustaw kursor na pozycji czwartej w wierszu górnym.
  lcd.write(0);         // Wyślij kolejno adresy górnych części grafik.
  lcd.write(2);
  lcd.write(4);
  lcd.write(6);
  lcd.setCursor(4, 1);  // Ustaw kursor na pozycji czwartej w wierszu dolnym.
  lcd.write(1);         // Wyślij kolejno adresy górnych części grafik.
  lcd.write(3);
  lcd.write(5);
  lcd.write(7);

  delay(40);  // Dobierz opóźnienie dla najlepszego efektu wizualnego.

  lcd.setCursor(4, 0);  // Wyczyść miejsca, w których tkwią lewe cyfry.
  lcd.print("    ");
  lcd.setCursor(4, 1);
  lcd.print("    ");

  lcd.createChar(0, znak + 32 * ((napiecie / 10) % 10));       // Lewa górna część dziesiątek.
  lcd.createChar(1, znak + 32 * ((napiecie / 10) % 10) + 8);   // Lewa dolna część dziesiątek.
  lcd.createChar(2, znak + 32 * ((napiecie / 10) % 10) + 16);  // Prawa górna część dziesiątek.
  lcd.createChar(3, znak + 32 * ((napiecie / 10) % 10) + 24);  // Prawa dolna część dziesiątek.
  lcd.createChar(4, znak + 32 * (napiecie % 10));              // Lewa górna część jednostek.
  lcd.createChar(5, znak + 32 * (napiecie % 10) + 8);          // Lewa dolna część jednostek.
  lcd.createChar(6, znak + 32 * (napiecie % 10) + 16);         // Prawa górna część jednostek.
  lcd.createChar(7, znak + 32 * (napiecie % 10) + 24);         // Prawa dolna część jednostek.

  lcd.setCursor(8, 0);  // Ustaw kursor na pozycji ósmej w wierszu górnym.
  lcd.write(0);         // Wyślij kolejno adresy górnych części grafik.
  lcd.write(2);
  lcd.write(4);
  lcd.write(6);
  lcd.setCursor(8, 1);  // Ustaw kursor na pozycji ósmej w wierszu dolnym.
  lcd.write(1);         // Wyślij kolejno adresy górnych części grafik.
  lcd.write(3);
  lcd.write(5);
  lcd.write(7);

  delay(40);  // Dobierz opóźnienie dla najlepszego efektu wizualnego.
}

Powrócimy znowu do pozyskania liczby napiecie w zakresie od zera do 1023, jak to było we wcześniejszych przykładach. Sposób rysowania nie będzie się tak naprawdę różnił wiele od metody z poprzedniego szkicu, tylko zostanie zdublowany. Zaczniemy od wyczyszczenia pól, na których ukazują się dwie ostatnie cyfry. Następnie zaprogramujemy wzorce, podobnie jak to było przed chwilą, ale używając obliczeń z przykładu z czterema cyframi, czyli korzystając z dzieleń przez tysiąc i sto oraz reszt z dzielenia przez dziesięć, w ten sposób programując pozycje tysięcy i setek.

Kolejno ustawimy się w miejscu, w którym mają się pojawić pierwsze dwie cyfry napięcia. Po wysłaniu kodów ASCII zamieramy na 40 milisekund. Potem powtarzamy całą procedurę, robiącą to samo, tylko w sposób lustrzany. A więc zamalowujemy spacjami to, co przed chwilą wyświetliliśmy, programujemy grafiki dla dziesiątek i jednostek, wysyłamy je w miejsca przeznaczenia i zamieramy ponownie na 40 ms.

Czas pauz dobrałem doświadczalnie dla Arduino Uno i posiadanego wyświetlacza. Niestety miga to wyraźnie. Obraz jest także nieco ciemny, co wynika z faktu dość długiego trwania w ciemnościach, gdy programują się znaki. Z ciekawości podmieniłem płytkę na Uno R4, zawierające znacznie szybszy mikrokontroler i obraz poprawił się. Z pewnością będzie jeszcze lepiej, gdy wyrzucimy zawiłe obliczenia poza okres ciemny, a programowanie znaków przeprowadzimy z adresów wcześniej obliczonych.

To oczywiście nie wszystkie sprytne metody na wyświetlacze, istnieje sposób na używanie specjalnie zaprojektowanych ośmiu grafik, z których można złożyć dowolną ilość cyfr, ale nie wyglądają one już tak dobrze. Ale o tym napiszę innym razem.

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