Pojďme programovat elektroniku: Postavíme si meteostanici s barevným TFT displejem

  • Dnes si vyzkoušíme základní práci s barevným displejem
  • Připojíme jej k Arduino Uno
  • A bude zobrazovat údaje z meteočidel

Ve výbavičce každého bastlíře nesmí chybět alespoň jeden pořádný displej. K těm nejoblíbenějším už roky patří titěrné 0,96“ monochromatické OLED moduly s ovladačem SSD1306, rozlišením 128×64 pixelů a komunikačním rozhraním I2C nebo SPI. Na internetu je seženete za pár dolarů.

Sice se mohou díky OLED technologii pochlubit docela nízkou spotřebou a díky absenci agresivního plošného podsvícení na nich může svítit třeba přesný čas i během noci, aniž by probudily celý dům, jenže co naplat, jejich aktivní zobrazovací plocha je menší než na současných chytrých hodinkách a zpravidla jedna jediná aktivní barva také brzy přestane bavit.

Barevný TFT LCD displej za dvě stovky

A tak k nim každý začínající bastlíř dříve či později dokoupí něco většího: třeba barevný TFT LCD. Jedním takovým je třeba 2,2“ displej s rozlišením 320×240 pixelů, 16bitovou barevnou hloubkou a ovladačem ILI9341, který podporuje celý zástup knihoven pro Arduino, přičemž jsou prakticky všechny do jedné odvozené z knihovny od Adafruitu a Adafruit GFX pro pokročilejší práci s grafikou.

Klepněte pro větší obrázek Klepněte pro větší obrázek Klepněte pro větší obrázek
2,2" TFT LCD displej s 16bitovou barevnou hloubkou, rozhraním SPI a integrovanou čtečkou SD karty, na které může být uložená třeba bitmapová grafika, na kterou je v titěrné flashové paměti ovládacích mikrokontrolerů málo místa.

Specialitou těchto modulů je také čtečka SD karet s rozhraním SPI na zadní straně. To se hodí pro případy, kdy chcete na displeji zobrazovat třeba jednoduché rastry, které se před zobrazením mohu načíst právě z paměťového média, aniž byste si jimi zaplnili už tak titěrnou flashovou paměť na většině mikrokontrolerů Arduino. Zároveň jsou často tyto displeje vybavené i dotykovou rezistivní vrstvou, čili si displej může sloužit i jako vstupní zařízení

Podobný (ale nedotykový) TFT displej dorazil před pár dny i do mé poštovní schránky, a tak se na něj dnes ve vší stručnosti podíváme. Přišel mě na všeho všudy 167 korun. Najdete samozřejmě i levnější, nicméně dříve, či později si na eBayi stejně najdete těch svých pár obchodníků, u kterých víte, že vám nezašlou pochybný šmejd.

Zároveň opět připomenu, že začátečník udělá nejlépe, když bude zpočátku nakupovat na českých e-shopech pro bastlíře. Je jich hromada od Arduino-shopu po Santy.cz. Zdánlivě vyšší ceny snadno obhájí zákonná záruka a často i podpora. Třeba zmíněný Arduino-shop provozuje web Arduino Návody.

SPI a hromada kablíků

Podobné displeje komunikují s mikropočítačem prakticky výhradně skrze sériové rozhraní SPI (Serial Peripheral Interface), které je rychlejší než I2C, avšak za cenu mnohem více vodičů. Zatímco I2C si vedle napájení vystačí už jen s datovou linkou SDA a synchronizační linkou SCL, rozhraní SPI potřebuje čtyři:

  • MISO (Master In Slave Out) pro zasílání dat z periférií do mikrokontroleru
  • MOSI (Master Out Slave In) pro zasílání dat z mikrokontroleru do periférií
  • SCK (Serial Clock) časovač pro synchronizaci komunikace
  • SS/CS (Slave Select/Chip Select) pro rozlišení jednotlivých periférií připojených na SPI. Mikrokontroler tímto vodičem může aktivovat zařízení, se kterým chce zrovna komunikovat, pokud jich budete mít zapojeno více.

(Pin MISO je v našem případě vlastně zbytečný, protože nám dnes půjde jen o jednosměrnou komunikaci z mikrokontroleru do displeje. MISO tedy nemusíme vůbec připojovat.)

Klepněte pro větší obrázek Klepněte pro větší obrázek
Pro připojení zařízení skrze rozhraní SPI lze použít i konektor ISCP, který je součástí mnoha prototypovacích deset Arduino. V ostatních případech je třeba se podívat, jaké GPIO piny mikrokontroleru slouží pro komunikaci SPI.

TFT displej ke všemu vedle základní komunikace skrze SPI disponuje ještě několika dalšími piny pro reset, podsvícení a ještě pinem DC/RS pro řízení komunikace. Suma sumárum, k zobrazení byť třeba jen jednoho pixelu, musíte zapojit devět kabelů, což je oproti čtyřem u I2C OLED displejů enormní množství, které spotřebuje skoro všechny GPIO na leckterém malém mikrokontroleru.

Postavíme meteostanici s displejem

Před pár týdny jsme si postavili drobnou meteostanici, která sice neměla displej, ale byla dostupná v lokální síti a běžel na ní webový server. Dnes si ji postavíme znovu, ale namísto webového serveru použijeme pro zobrazení barevný TFT displej.

Klepněte pro větší obrázek Klepněte pro větší obrázek Klepněte pro větší obrázek
Meteostanice s TFT LCD displejem, externími hodinami s vlastní baterií a tlakoměrem, teploměrem, vlhkoměrem a luxmetrem

Displej bude po spuštění svítit vždy deset sekund. Poté se přepne do „úsporného režimu“ – jednoduše zhasne jeho podsvícení podobně jako na starém mobilním telefonu. Opět jej budu moci rozsvítit klepnutím na modul dotykového tlačítka.

Na displeji se vykreslí přesný čas z RTC a šachovnice čtyř dlaždic s meteorologickými údaji. Tyto dlaždice a čas se budou každou minutu překreslovat. Na rozdíl od maličkých OLED se nepřekresluje celá obrazovka, ale jen konkrétní oblast displeje.

Meteostanice bude vybavená těmito senzory, které budou všechny komunikovat na sběrnici I2C:

Tentokrát nebude meteostanice připojená do internetu, vše totiž bude postavené na základní prototypovací destičce Arduino Uno (respektive levném klonu z eBaye). S malými úpravami bych ale mohl použít třeba velké NodeMCU s Wi-Fi čipem ESP8266.

Rastrové fonty

Aby to všechno na displeji vypadalo alespoň trošku hezky, použiji už zmíněnou knihovnu od Adafruitu a to včetně jejího rozšíření Adafruit GFX, pomocí které budu moci pracovat namísto toho základního a primitivního s „HD“ rastrovými fonty.

Klepněte pro větší obrázek Klepněte pro větší obrázek Klepněte pro větší obrázek
Rastrové písmo je zpravidla konkrétní řez běžného vektorového fontu převedený do pole pixelů s jednotlivými znaky

Rastrové písmo je vlastně pole, ve kterém jsou uložené pixely běžného fontu v jeho konkrétním řezu. Ale pozor, zpravidla nikoliv všechny znaky, ale jen základní ASCII. Pokud byste chtěli českou diakritiku a cokoliv dalšího speciálního, budete si muset podobné pole vytvořit sami třeba pomocí mnoha dostupných konvertorů.

Má to své opodstatnění, rastrový font písma Free Sans ve velikosti 9 pro knihovnu Adafruit GFX totiž představuje takto rozměrné pole. Kdybyste takových písem a pokaždé v různých velikostech řezu chtěli použít více, na jednoduchých čipech ATmega, které pohánějí základní modely prototypovacích destiček Arduino, by vám brzy došla flashová paměť.

Tento problém řeší některé další knihovny třeba tak, že si v generátoru rastrového písma zvolíte jen ty znaky, které budete zaručeně používat.

V každém případě, web je plný již převedených fontů pro Adafruit GFX – stačí použít Google – a knihovna samotná jich v základu už několik obsahuje. Jsou uložené jako hlavičkové soubory *.h v podadresáři knihovny Fonts/. Hlavičkový soubor pro další fonty si můžete vygenerovat třeba pomocí tohoto webového formuláře.

Klepněte pro větší obrázek
Adafruit GFX nabízí hromadu základních rastrových písem vždy v několika velikostech

Takže fonty bychom měli a teď už stačí pouze již obvyklou cestou přečíst hodnoty ze senzorů a vypsat je na rozměrný displej. Senzory umístím ne nepájivé pole, kde zbude místo i pro modul RTC hodin DS3231 pro zobrazení přesného času a dotykové tlačítko. Když na něj klepnu, zhasne, nebo se naopak rozsvítí podsvícení TFT displeje.

Zdrojový kód meteostanice s TFT displejem

A to je celé, zbytek už opět napoví komentovaný kód jednoduchého programu.

// Knihovny pro praci s rozhranim SPI a TFT displejem
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

// Rastrovy font Open Sans Condensed Bold ve velikosti 25
#include "Fonts/Open_Sans_Condensed_Bold_25.h"
// Rastrovy font Free Sans ve velikosti 9
#include "Fonts/FreeSans9pt7b.h"

// Knihovny pro praci s rozhranim I2C a meteorologickymi senzory
#include "Wire.h"
#include "cactus_io_BME280_I2C.h" // Tlakomer BME280
#include "Adafruit_SHT31.h" // Teplomer SHT31
#include "BH1750.h" // Luxmetr BH1750
#include "DS3231.h" // Hodiny DS3231

// Knihovna pro snazsi zapis do seriove linky
#include "Streaming.h"

/*
   Displej pripojim na standardni piny SPI daneho mikrokontroleru
   Zbyvajici piny CS a DC/RS treba takto:
*/
#define TFT_DC 9
#define TFT_CS 10

// Piny dotykoveho tlacitka a podsviceni displeje
#define TLACITKO 7
#define TFT_LED 2

// Konstanty pro vykresleni dlazdic
#define DLAZDICE_POSUN_Y 80
#define DLAZDICE_ODSAZENI_TEXT 10
#define DLAZDICE_ODSAZENI 10
#define DLAZDICE_VELIKOST 105

// Objekty TFT dislpeje, meteorologickych senzoru a hodin
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
BME280_I2C tlakomer(0x76);
Adafruit_SHT31 teplomer = Adafruit_SHT31();
BH1750 luxmetr;
RTClib hodiny;

// Pomocne promenne se stavem podsviceni a stisku tlacitka
bool podsviceni = true;
bool dotyk =  false;
uint64_t podsviceniStart = 0;

// Pomocna promenna pro mereni a prekreslovani displeje kazdou minutu
uint64_t dalsiObnova = 0;

// Promenne meteorologickych velicin
uint16_t svetlo;
float tlak;
float teplota;
uint16_t vlhkost;

// Funkce setup se zpracuje po spusteni mikropocitace
void setup()
{
  // Start seriove linky
  Serial.begin(115200);

  // Start TFT displeje a nastaveni orientace podle potreby
  tft.begin();
  tft.setRotation(2);

  // Start luxmetru, teplomeru a tlakomeru
  luxmetr.begin();
  teplomer.begin(0x44);
  tlakomer.begin();

  /*
     Nastaveni pinu dotykoveho tlacitka na vstup a pinu
     podsviceni na vystup. Pri napajeni podsviceni pinem
     GPIO je treba dat pozor, aby odber proudu neprekonal
     limit GPIO.
  */
  pinMode(TLACITKO, INPUT);
  pinMode(TFT_LED, OUTPUT);

  // Prekresleni TFT displeje cernou barvou
  tft.fillScreen(ILI9341_BLACK);
  // Nastaveni barvy pisma na bilou
  tft.setTextColor(ILI9341_WHITE);
  // Nastaveni pisma na Open Sans Condensed Bold
  tft.setFont(&Open_Sans_Condensed_Bold_25);

  // Vypsani textu na stred displeje
  uint16_t sirka, vyska;
  ziskejRozmery("METEOSTANICE", &sirka, &vyska);
  tft.setCursor(120 - (sirka / 2), 30);
  tft.print("METEOSTANICE");

  // Zapnout podsviceni TFT displeje
  nastavitPodsviceni(true);
}

// Funkce loop se opakuje stale dokola
void loop() {
  // Zmer hodnoty, prekresli displej a opakuj za 60 sekund
  if (millis() > dalsiObnova) {
    zmeritData();
    dalsiObnova = millis() + 60000;
  }

  /*
     Pokud se dotknu dotykoveho tlacitka,
     zapni, nebo naopak vypni podsviceni.
     Ke zmene dojde pote, co skonci samotny dotyk,
     nikoliv na jeho zacatku.
  */
  if (digitalRead(TLACITKO)) {
    dotyk = true;
  }
  else {
    if (dotyk) {
      podsviceni = !podsviceni;
      nastavitPodsviceni(podsviceni);
      dotyk = false;
    }
  }

  /*
      Pokud obrazovka sviti vice nez 10 sekund,
      vypni podsviceni
  */
  if (podsviceni) {
    if ((millis() - podsviceniStart) > 10000) {
      nastavitPodsviceni(false);
    }
  }
}

/*
    Funkce, ktera ziska data ze senzoru, vypise je do seriove linky
    a nakonec vykresli sachovnici barevnych dlazdic na TFT displeji
*/
void zmeritData() {
  // Precteni hodnot ze senzoru
  tlakomer.readSensor();
  svetlo = luxmetr.readLightLevel();
  tlak = tlakomer.getPressure_MB();
  teplota = teplomer.readTemperature();
  vlhkost = teplomer.readHumidity();

  // Vypsani hodnot do seriove lniky pro kontrolu
  Serial << "Luxmetr: " << svetlo << endl;
  Serial << "Tlak: " << tlak << endl;
  Serial << "Teplota: " << teplota << endl;
  Serial << "Vlhkost: " << vlhkost << endl;

  /*
      Funkce pro vykresleni dlazdice na jedne ze ctyr fixnich pozic s
      nadpisem, podnadpisem, hodnotou (float, int nebo char[]) a barvou
      pozadi, ktere bych mohl menit podle zmerenych hodnot (ruda pro
      vedro, modra pro chlad aj.). Jelikoz vykresleni zabere nejaky
      cas, vznikne docela hezka animace postupne se vykreslujicich
      dlazdic.
  */
  nakresliDlazdici(0, "TEPLOTA", "(st. C)", teplota,
                   tft.color565(255, 0, 0));
  nakresliDlazdici(1, "VLHKOST", "(%)", vlhkost,
                   tft.color565(0, 0, 255));
  nakresliDlazdici(2, "TLAK", "(hPa)", tlak,
                   tft.color565(0, 255, 0));
  nakresliDlazdici(3, "SLUNCE", "(lx)", svetlo,
                   tft.color565(255, 255, 0));

  // Nakresli vycentrovany text s aktualnim casem 50px od horniho okraje
  nakresliCas(40);
}

/* Pomocne pretizene funkce pro rozliseni, jestli se jedna o blok
   s promennou celeho cisla, nebo cisla s desetinou carkou
*/
void nakresliDlazdici(uint8_t pozice, char nadpis[], char podnadpis[],
                      float hodnota, uint16_t barva) {
  // Prevod ciselne hodnoty float na retezec
  char strHodnota[8];
  dtostrf(hodnota, 4, 2, strHodnota);
  nakresliDlazdici(pozice, nadpis, podnadpis, strHodnota, barva);
}

void nakresliDlazdici(uint8_t pozice, char nadpis[], char podnadpis[],
                      uint16_t hodnota, uint16_t barva) {
  // Prevod ciselne hodnoty int na retezec
  char strHodnota[8];
  itoa(hodnota, strHodnota, 10);
  nakresliDlazdici(pozice, nadpis, podnadpis, strHodnota, barva);
}

// Samotna funkce pro vykresleni barevne dlazdice
void nakresliDlazdici(uint8_t pozice, char nadpis[], char podnadpis[],
                      char hodnota[], uint16_t barva) {
  // Souradnie dlazdice
  uint16_t x = 0;
  uint16_t y = 0;

  // Souradnice dlazdice podle jedne ze ctyr moznych pozic (0 az 3)
  switch (pozice) {
    case 0:
      x = DLAZDICE_ODSAZENI;
      y = DLAZDICE_POSUN_Y;
      break;
    case 1:
      x = (DLAZDICE_ODSAZENI * 2) + DLAZDICE_VELIKOST;
      y = DLAZDICE_POSUN_Y;
      break;
    case 2:
      x = DLAZDICE_ODSAZENI;
      y = DLAZDICE_POSUN_Y + DLAZDICE_VELIKOST + DLAZDICE_ODSAZENI;
      break;
    case 3:
      x = (DLAZDICE_ODSAZENI * 2) + DLAZDICE_VELIKOST;
      y = DLAZDICE_POSUN_Y + DLAZDICE_VELIKOST + DLAZDICE_ODSAZENI;
      break;
  }

  // Vykresleni stinu a dlazdice
  tft.fillRoundRect(x + 3, y + 3, DLAZDICE_VELIKOST, DLAZDICE_VELIKOST,
                    10, ILI9341_DARKGREY);
  tft.fillRoundRect(x, y, DLAZDICE_VELIKOST, DLAZDICE_VELIKOST,
                    10, barva);

  // Vycentrovani a vykresleni nadpisu a podnadpisu dlazdice
  uint16_t sirka, vyska;
  tft.setTextColor(ILI9341_BLACK);
  tft.setFont(&FreeSans9pt7b);
  ziskejRozmery(nadpis, &sirka, &vyska);
  tft.setCursor(x + ((DLAZDICE_VELIKOST / 2) - (sirka / 2)),
                y + DLAZDICE_ODSAZENI_TEXT + vyska);
  tft.print(nadpis);
  ziskejRozmery(podnadpis, &sirka, &vyska);
  tft.setCursor(x + ((DLAZDICE_VELIKOST / 2) - (sirka / 2)),
                y + DLAZDICE_ODSAZENI_TEXT + (vyska * 2));
  tft.print(podnadpis);

  // Vycentrovani a vykresleni hlavni hodnoty
  tft.setFont(&Open_Sans_Condensed_Bold_25);
  ziskejRozmery(hodnota, &sirka, &vyska);
  tft.setCursor(x + ((DLAZDICE_VELIKOST / 2) - (sirka / 2)),
                y + DLAZDICE_ODSAZENI_TEXT + ((DLAZDICE_VELIKOST / 2)
                    + vyska));
  tft.print(hodnota);
}

/*
   Funkce pro ziskani rozmeru hypotetickeho vykresleneho textu
   Pouziji ji pro vypocet pozice kurzoru pro centrovani na stred
*/
void ziskejRozmery(char text[], uint16_t* sirka, uint16_t* vyska) {
  int16_t  x1, y1;
  tft.getTextBounds(text, 0, 0, &x1, &y1, sirka, vyska);
}

// Nastaveni podsviceni TFT displeje
void nastavitPodsviceni(bool zapnuto) {
  if (zapnuto) {
    digitalWrite(3, HIGH);
    podsviceniStart = millis();
  }
  else {
    digitalWrite(3, LOW);
  }
  podsviceni = zapnuto;
}

// Vycentrovani a vykresleni casu z RTC na pozici Y
void nakresliCas(uint16_t y) {
  uint16_t sirka, vyska;
  char txtCas[21];
  DateTime cas = hodiny.now();
  snprintf(txtCas, sizeof(txtCas), "%02d.%02d. %d %02d:%02d",
           cas.day(), cas.month(), cas.year(), cas.hour(),
           cas.minute());
  tft.setFont(&FreeSans9pt7b);
  ziskejRozmery(txtCas, &sirka, &vyska);
  tft.setTextColor(ILI9341_WHITE);
  tft.setCursor(120 - (sirka / 2), y + vyska);
  tft.fillRect(0, y, 240, vyska + 5, ILI9341_BLACK);
  tft.print(txtCas);
  Serial << "aktualni cas: " << txtCas << endl;
}

Témata článku: Pojďme programovat elektroniku, Programování, Arduino, Zajímavosti, Programování pro děti, Stavebnice, LCD, C++, Počasí, Přesná pozice, Print, Setup, TXT, Input, Nadpis, Modrá, OLED, STM, TFT, Základní prototyp, Cactus, Přesná hodnota, Arduino Uno, Vlhkost, Pořádný displej

Určitě si přečtěte


Aktuální číslo časopisu Computer

26 procesorů v důkladném testu

Zhodnotili jsme 18 bezdrátových reproduktorů

Jak fungují cash back služby?

Pohlídejte své děti na internetu