Kombinovaný snímač teploty a vlhkosti DHT22
Zo stránky SensorWiki
Záverečný projekt predmetu MIPS / LS2025 - Martin Hubocký
Zadanie
Mojím zadaním bolo vytvoriť program, ktorý bude zo senzoru DHT22 (Snímač teploty a vlhkosti) čítať a spracovávať dáta a cez UART sériovú komunikáciu ich vypísať na obrazovku monitoru. V prípade že sa mi to podarí spravím rozšírenie programu aby sa dáta zobrazovali na displeji typu LCD (Liquid Crystal Display).

Analýza a opis riešenia
Postup vypracovávania projektu a opis činnosti zariadenia
Najskôr som vo vývojovom prostredí Arduino IDE overil funkčnosť snímača. V tomto prostredí sa nachádza množstvo knižníc, takže mi "spojazdnenie" snímaču trvalo pár minút. Keď som si bol istý že snímač funguje, prešiel som do prostredia AVR, kde to bez knižníc bolo trochu tažšie (trochu dosť :) ). Najskôr sa mi dlhú dobu nedarilo, potom sa mi podarilo cez UART vypísať celočíselné hodnoty, a napokon aj desatinné miesta. Na komunikáciu cez UART som samozrejme použil knižnicu, ktorú sme si vytvárali na cvičení z predmetu.
Ďalej som postupoval s pripojením displeja k mikrokontroléru podľa návodu na stránke (Tu), kde bol takisto aj link na stiahnutie knižnice lcd_ch.h (a lcd_ch.c), ktorú takisto vo svojom programe používam. Vytvoril (zadefinoval) som si dva vlastné znaky. 1. bol znak "°" (Stupeň Celzia), a 2. bol znak "ť" ktorý sa mi nepodaril práve najkrajšie (Pretože displej ktorý vo svojom projekte používam vykresľuje jeden znak pomocou políčka o rozmeroch 5x7 pixelov a nie 5x8 pixelov ako niektoré iné modely). Takisto som vypol kurzor displeju pretože mi osobne vadil. Zo začiatku som si myslel že displej nefunguje, no neskôr som si uvedomil svoju amatérsku chybu keď som zistil že som pomocou potenciometra nemal nastavený správny jas. Okrem toho som s displejom žiadny iný problém nemal.
Týmto som mal hlavnú úlohu svojho projektu splnenú, no rozhodol som sa ho ešte obohatiť o svetelnú a zvukovú signalizáciu. Do zapojenia som pridal dve RGB LED diódy (aj s rezistormi k nim) a jedno Piezo. Jedna dióda mala signalizovať stav teploty a druhá stav vlhkosti. Každá má 5 stavov:
1. Veľmi nízka hodnota - Biela farba - (1. Teplota je nižšia ako 28 °C), (2. Vlhkosť je nižšia ako 50 %)
2. Nízka hodnota - Modrá farba - (1. Teplota je v rozmedzí 28 - 32 °C), (2. Vlhkosť je v rozmedzí 50 - 60 %)
3. Stredná hodnota - Zelená farba - (1. Teplota je v rozmedzí 32 - 36 °C), (2. Vlhkosť je v rozmedzí 60 - 70 %)
4. Vysoká hodnota - Červená farba - (1. Teplota je v rozmedzí 36 - 40 °C), (2. Vlhkosť je v rozmedzí 70 - 80 %)
5. Veľmi vysoká hodnota - Fialová farba - (1. Teplota je vyššia ako 40 °C), (2. Vlhkosť je vyššia ako 80 %)
Úlohou pieza je signalizovať zmenu týchto 5 stavov (pípaním). Keďže piezo je len jedno, signalizuje zmenu stavu aj pre teplotu aj pre vlhkosť, no rozdiel je v tom že pri zmene stavu teploty spraví "krátke pípnutie" a pri zmene vlhkosti "dlhé pípnutie". Piezo rozlišuje aj to, či sa stav zmenil smerom nahor alebo nadol (teda napríklad či sa teplota zvýšila alebo znížila), a to tak, že pri zmene stavu nahor (inkrementácii), pípne dva krát a pri zmene stavu nadol (dekrementácii), pípne len raz. Teda ak sa napríklad zvýši stav vlhkosti, piezo spraví dve dlhé pípnutia, ak sa napríklad zníži stav teploty piezo spraví jedno krátke pípnutie a pod. Ak sa stane že teplota alebo vlhkosť dosiahne posledný 5. stav (najvyšší), piezo bude pípať "stále" (v skutočnosti bude pípať 5x v každej main() slučke programu). V tomto prípade som nepredpokladal že by som pri mojom "amatérskom testovaní" mohol vytvoriť také podmienky, že by mohlo dôjsť k 5. stavu teploty aj vlhkosti súčasne keďže zo zvyšujúcou sa teplotou vlhkosť klesá a zas naopak.
Poslednou vecou ktorú som spravil bolo, že som si zo starej stavebnice poskladal držiak na ktorý som umiestnil snímač aby som si uľahčil testovaciu (overovaciu) časť tohto projektu.

Schéma zapojenia a jej popis
Mikrokontrolér Arduino UNO R3
- MASTER (riadiaci orgán celého zariadenia)
- sú k nemu pripojené všetky ostatné zariadenia a súčiastky použité v projekte
- je napájaný priamo z notebooku pomocou USB kábla (a zároveň je ním aj programovaný)
Snímač teploty a vlhkosti DHT22
- má 3 piny:
VCC - pripojený k 5V pinu mikrokontroléra GND - pripojený ku GND (Zem) pinu mikrokontroléra DATA - pripojený k D8 pinu mikrokontroléra --> Cez tento pin prichádzajú do mikrokontroléra dáta v tvare: [Vlhkosť celé číslo, Vlhkosť desatinné číslo, Teplota celé číslo, Teplota desatinné číslo, Kontrolný súčet] [8 bitov, 8 bitov, 8 bitov, 8 bitov, 8 bitov; 8 + 8 + 8 + 8 + 8 = 40 bitový prenos]
Liquid Crystal Display typu DEM16216
- má 16 pinov:
VSS - pripojený ku GND (Zem) pinu mikrokontroléra VDD - pripojený k 5V pinu mikrokontroléra --> Piny VSS a VDD slúžia na napájanie displeja VO - je naň prostredníctvom potenciometra privedené napätie --> Týmto pinom sa v závislosti od privedeného napätia nastavuje jas (kontrast) RS - je pripojený k D2 pinu mikrokontroléra --> Slúži na výber registra (Command RS=0; DATA RS=1) R/W - je pripojený k D3 pinu mikrokontroléra --> Slúži na výber medzi čítaním alebo zápisom (READ R/W=1; WRITE R/W=0) E - je pripojený k D4 pinu mikrokontroléra --> Slúži na spustenie časového signálu (Hovorí displeju kedy má čítať) (Reaguje pri dobežnej hrane) D0-D3 - sú nezapojené (pretože nepoužívam 8 bitový mód ale len 4 bitový) D4-D7 - sú zapojené k pinom D9-D12 mikrokontroléru --> Slúžia na 4 bitový prenos dát A - pripojený ku GND (Zem) pinu mikrokontroléra K - pripojený k 5V pinu mikrokontroléra --> Piny A a K slúžia na zapnutie podsvietenia displeja
RGB LED Diódy
- každá má 4 piny:
R - sú cez odpory R6 a R3 (220 Ω) pripojené k A0 (LED1) a A3 (LED2) pinom mikrokontroléra --> Slúžia na zapínanie červenej LED diódy (privedením log. 1) G - sú cez odpory R5 a R2 (220 Ω) pripojené k A1 (LED1) a A4 (LED2) pinom mikrokontroléra --> Slúžia na zapínanie zelenej LED diódy (privedením log. 1) B - sú cez odpory R4 a R1 (220 Ω) pripojené k A2 (LED1) a A5 (LED2) pinom mikrokontroléra --> Slúžia na zapínanie modrej LED diódy (privedením log. 1) GND - obidva piny (LED1 aj LED2) sú pripojené ku GND (Zem) pinu mikrokontroléra
Piezo
- má 2 piny:
VCC - pripojený k D13 pinu mikrokontroléra --> V závislosti od signálu vydá piezo zvuk (V mojom prípade je to vždy log. 1 a teda frekvencia zvuku je vždy rovnaká) GND - pripojený ku GND (Zem) pinu mikrokontroléra

Algoritmus a program
Vo svojom hlavnom programe som si vytvoril nasledovné funkcie:
- void lcd_print_float(float hodnota);
- void Vlastne_znaky(void);
- void RGB_teplota(uint8_t cervena, uint8_t zelena, uint8_t modra);
- void RGB_vlhkost(uint8_t cervena, uint8_t zelena, uint8_t modra);
- void Farba_teplota(float teplota);
- void Farba_vlhkost(float vlhkost);
- void Pipnutie(uint8_t pocet_pipnuti);
- void Pipnutie_dlhe(uint8_t pocet_pipnuti);
- uint8_t Precitaj_senzor(void);
Myslím si, že som svoj kód prehľadne okomentoval a preto nebudem v tejto časti vysvetľovať jeho funkčnosť.
Okrem týchto funkcií nepriamo využívam aj pár pomocných funkcií z knižníc. Tie používam dve:
- uart.h (a uart.c) - Túto knižnicu sme si robili na cvičení z predmetu počas semestru
- lcd_ch.h (a lcd_ch.c) - Túto knižnicu som podľa dohody s vyučujúcim použil z web-stránky ktorej odkaz je uvedený na spodku pod názvom "Práca s displejom"
Tu je výpis celého kódu aj spolu s knižnicami:
// Semestrálny projekt z predmetu Mikropočítačové systémy (MIPS)
// Téma: Kombinovaný snímač teploty a vlhkosti DHT22
// Vypracoval: Martin Hubocký
// Školský rok: 2024/2025
// ======================[ Knižnice (+ frekvencia) ]======================
#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include "uart.h"
#include "lcd_ch.h"
// ======================[ Definície a premenné ]======================
#define Snimac_DHT22 PB0
uint8_t Pole_udajov[5];
#define RGB_teplota_cervena PC0
#define RGB_teplota_zelena PC1
#define RGB_teplota_modra PC2
#define RGB_vlhkost_cervena PC3
#define RGB_vlhkost_zelena PC4
#define RGB_vlhkost_modra PC5
#define Piezo PB5
int aktualny_stav_teplota = 0;
int posledny_stav_teplota = 0;
int aktualny_stav_vlhkost = 0;
int posledny_stav_vlhkost = 0;
// ======================[ Deklarácie pomocných funkcií ]======================
// (Bez tohto to malo problém s kompiláciou lebo mám funkcie definované až za main(), pretože mi to osobne prišlo prehľadnejšie)
void lcd_print_float(float hodnota);
void Vlastne_znaky(void);
void RGB_teplota(uint8_t cervena, uint8_t zelena, uint8_t modra);
void RGB_vlhkost(uint8_t cervena, uint8_t zelena, uint8_t modra);
void Farba_teplota(float teplota);
void Farba_vlhkost(float vlhkost);
void Pipnutie(uint8_t pocet_pipnuti);
void Pipnutie_dlhe(uint8_t pocet_pipnuti);
uint8_t Precitaj_senzor(void);
// ======================[ Hlavná funkcia main() ]======================
int main(void) {
// Inicializácia výstupov RGB LED-iek a Pieza
DDRC |= (1<<RGB_teplota_cervena) | (1<<RGB_teplota_zelena) | (1<<RGB_teplota_modra) | (1<<RGB_vlhkost_cervena) | (1<<RGB_vlhkost_zelena) | (1<<RGB_vlhkost_modra);
DDRB |= (1 << Piezo);
uart_init(); // Inicializácia sériovej komunikácie (UART)
lcd_init(); // Inicializácia displeju
lcd_command(0x0C); // Vypnutie kurzoru
Vlastne_znaky(); // Definovanie vlastných znakov (°C a ť)
char buffer[32], pole_teplota[10], pole_vlhkost[10]; // Definície pomocných polí a ich dĺžok (budú využité neskôr v programe (UART))
uart_puts("Spustenie\n");
_delay_ms(1000);
Precitaj_senzor(); // Načítam dáta zo senzoru teploty a vlhkosti (DHT22)
_delay_ms(2000);
while (1) {
_delay_ms(2000);
if (Precitaj_senzor()) { // Ak sa podarí načítať dáta zo senzoru...
// Výpočet vlhkosti (resp. spracovanie do float premennej)
uint16_t raw_vlhkost = ((uint16_t)Pole_udajov[0] << 8) | Pole_udajov[1];
float vlhkost = raw_vlhkost / 10.0;
// Výpočet teploty (resp. spracovanie do float premennej)
uint16_t raw_teplota = ((uint16_t)Pole_udajov[2] << 8) | Pole_udajov[3];
float teplota = raw_teplota / 10.0;
if (Pole_udajov[2] & 0x80) { // Ak je teplota záporná uloží znamienko mínus
teplota = -((raw_teplota & 0x7FFF) / 10.0);
}
// Výpis cez UART
dtostrf(teplota, 5, 1, pole_teplota); // Uloží hodnotu teploty do pole_teplota ktoré sa vypíše cez UART
dtostrf(vlhkost, 5, 1, pole_vlhkost); // Uloží hodnotu vlhkosti do pole_vlhkost ktoré sa vypíše cez UART
sprintf(buffer, "Teplota: %s C\n", pole_teplota);
uart_puts(buffer);
sprintf(buffer, "Vlhkost: %s %%\n", pole_vlhkost);
uart_puts(buffer);
// Výpis na displej
// (1. Riadok displeju)
lcd_command(0x80);
zob_text("Teplota:");
Vypis_na_displej(teplota);
lcd_data(1); // znak °
lcd_data('C');
// (2. Riadok displeju)
lcd_command(0xC0);
zob_text("Vlhkos");
lcd_data(2); // znak ť
lcd_data(':');
Vypis_na_displej(vlhkost);
lcd_data('%');
// Nastavenie farieb RGB LED-iek
Farba_teplota(teplota);
Farba_vlhkost(vlhkost);
}
else { // Ak by došlo k chybe pri načítaní dát zo senzoru... (Len sa vypíše na UART a displej)
uart_puts("Chyba pri nacitani dat zo senzoru (DHT22)\n");
lcd_command(0x80);
zob_text("Chyba senzora ");
lcd_command(0xC0);
zob_text(" ");
}
}
}
// ======================[ Funkcie pre farebnú signalizáciu a zvukovú signalizáciu ]======================
// Prevedie (Zasvieti) požadovanú farbu RGB diódy teploty na "kód"
void RGB_teplota(uint8_t cervena, uint8_t zelena, uint8_t modra) { // Ak má príslušná farba RGB LED-ky zasvietiť musím zapísať log. 1, inak nesvieti
if (cervena) PORTC |= (1<<RGB_teplota_cervena); else PORTC &= ~(1<<RGB_teplota_cervena);
if (zelena) PORTC |= (1<<RGB_teplota_zelena); else PORTC &= ~(1<<RGB_teplota_zelena);
if (modra) PORTC |= (1<<RGB_teplota_modra); else PORTC &= ~(1<<RGB_teplota_modra);
}
// Prevedie (Zasvieti) požadovanú farbu RGB diódy vlhkosti na "kód"
void RGB_vlhkost(uint8_t cervena, uint8_t zelena, uint8_t modra) { // Ak má príslušná farba RGB LED-ky zasvietiť musím zapísať log. 1, inak nesvieti
if (cervena) PORTC |= (1<<RGB_vlhkost_cervena); else PORTC &= ~(1<<RGB_vlhkost_cervena);
if (zelena) PORTC |= (1<<RGB_vlhkost_zelena); else PORTC &= ~(1<<RGB_vlhkost_zelena);
if (modra) PORTC |= (1<<RGB_vlhkost_modra); else PORTC &= ~(1<<RGB_vlhkost_modra);
}
// Na základe veľkosti hodnoty teploty vyhodnotí príslušnú farbu ktorou má RGB LED-ka svietiť, stav teploty (5 stavov (28°C; 40°C; interval 4°C)) a ak došlo k zmene stavu vyhodnotí smer zmeny t.j. či teplota stúpla alebo klesla (využije sa neskôr pri zvukovej signalizácii)
void Farba_teplota(float teplota) {
// Vyhodnotenie veľkosti hodnoty teploty a priradenie stavu
if (teplota <= 28.0) { // Ak je teplota menšia alebo rovná ako 28°C zaviesti biela farba a určí stav teploty ako stav 1
RGB_teplota(1, 1, 1); aktualny_stav_teplota = 1;
} else if (teplota <= 32.0) { // Ak je teplota menšia alebo rovná ako 32°C zaviesti modrá farba a určí stav teploty ako stav 2
RGB_teplota(0, 0, 1); aktualny_stav_teplota = 2;
} else if (teplota <= 36.0) { // Ak je teplota menšia alebo rovná ako 36°C zaviesti zelená farba a určí stav teploty ako stav 3
RGB_teplota(0, 1, 0); aktualny_stav_teplota = 3;
} else if (teplota <= 40.0) { // Ak je teplota menšia alebo rovná ako 40°C zaviesti červená farba a určí stav teploty ako stav 4
RGB_teplota(1, 0, 0); aktualny_stav_teplota = 4;
} else { // Ak je teplota vaačšia ako 40°C zaviesti fialová farba a určí stav teploty ako stav 5
RGB_teplota(1, 0, 1); aktualny_stav_teplota = 5; // (Keďže tento stav sa považuje "akože" za nebezpečný, piezo bude pípať (Nebude pípať nonstop ale bude pípať za každým keď program main() spraví jeden cyklus))
Pipnutie(5);
}
// Vyhodnotenie zmeny a smeru zmeny teploty
if (aktualny_stav_teplota > posledny_stav_teplota) Pipnutie(2); // Ak teplota stúpla piezo pípne dva krát (len ak došlo k zmene)
else if (aktualny_stav_teplota < posledny_stav_teplota) Pipnutie(1); // Ak teplota klesla piezo pípne jeden krát (len ak došlo k zmene)
posledny_stav_teplota = aktualny_stav_teplota;
}
// Na základe veľkosti hodnoty vlhkosti vyhodnotí príslušnú farbu ktorou má RGB LED-ka svietiť, stav vlhkosti (5 stavov (50%; 80%; interval 10%)) a ak došlo k zmene stavu vyhodnotí smer zmeny t.j. či vlhkosť stúpla alebo klesla (využije sa neskôr pri zvukovej signalizácii)
void Farba_vlhkost(float vlhkost) {
// Vyhodnotenie veľkosti hodnoty vlhkosti a priradenie stavu
if (vlhkost <= 50.0) { // Ak je vlhkosť menšia alebo rovná ako 50% zaviesti biela farba a určí stav vlhkosti ako stav 1
RGB_vlhkost(1, 1, 1); aktualny_stav_vlhkost = 1;
} else if (vlhkost <= 60.0) { // Ak je vlhkosť menšia alebo rovná ako 60% zaviesti modrá farba a určí stav vlhkosti ako stav 2
RGB_vlhkost(0, 0, 1); aktualny_stav_vlhkost = 2;
} else if (vlhkost <= 70.0) { // Ak je vlhkosť menšia alebo rovná ako 70% zaviesti zelená farba a určí stav vlhkosti ako stav 3
RGB_vlhkost(0, 1, 0); aktualny_stav_vlhkost = 3;
} else if (vlhkost <= 80.0) { // Ak je vlhkosť menšia alebo rovná ako 80% zaviesti červená farba a určí stav vlhkosti ako stav 4
RGB_vlhkost(1, 0, 0); aktualny_stav_vlhkost = 4;
} else { // Ak je vlhkosť väčšia ako 80% zaviesti fialová farba a určí stav vlhkosti ako stav 5
RGB_vlhkost(1, 0, 1); aktualny_stav_vlhkost = 5; // (Keďže tento stav sa považuje "akože" za nebezpečný, piezo bude pípať (pípnutie je dlhšie ako pri teplote) (Nebude pípať nonstop ale bude pípať za každým keď program main() spraví jeden cyklus))
Pipnutie_dlhe(5);
}
// Vyhodnotenie zmeny a smeru zmeny vlhkosti
if (aktualny_stav_vlhkost > posledny_stav_vlhkost) Pipnutie_dlhe(2); // Ak vlhkost stúpla piezo pípne dva krát (pípnutie je dlhšie ako pri teplote) (len ak došlo k zmene)
else if (aktualny_stav_vlhkost < posledny_stav_vlhkost) Pipnutie_dlhe(1); // Ak vlhkost stúpla piezo pípne jeden krát (pípnutie je dlhšie ako pri teplote) (len ak došlo k zmene)
posledny_stav_vlhkost = aktualny_stav_vlhkost;
}
// ======================[ Piezo reproduktor ]======================
// Pípanie pre teplotu
void Pipnutie(uint8_t pocet_pipnuti) {
for (uint8_t i = 0; i < pocet_pipnuti; i++) { // Piezo pípa požadovaný "pocet_pipnuti" krát pre teplotu t. j. pípa krátko (100ms hučí a 100ms je ticho)
PORTB |= (1 << Piezo);
_delay_ms(100);
PORTB &= ~(1 << Piezo);
_delay_ms(100);
}
}
// Pípanie pre vlhkosť
void Pipnutie_dlhe(uint8_t pocet_pipnuti) { // Piezo pípa požadovaný "pocet_pipnuti" krát pre vlhkosť t. j. pípa dlho (500ms hučí a 100ms je ticho)
for (uint8_t i = 0; i < pocet_pipnuti; i++) {
PORTB |= (1 << Piezo);
_delay_ms(500);
PORTB &= ~(1 << Piezo);
_delay_ms(100);
}
}
// ======================[ Čítanie zo senzora DHT22 ]======================
uint8_t Precitaj_senzor() {
uint8_t bity[5] = {0}; // Pole pre 5 bajtov prijatých zo senzora (po 8 bitov) (2 bajty pre vlhkosť (celé číslo, desatinné číslo), 2 bajty pre teplotu (celé číslo, desatinné číslo), 1 bajt pre kontrolný súčet)
uint8_t i, j;
// Odoslanie "Štartovacieho" signálu
DDRB |= (1 << Snimac_DHT22);
PORTB &= ~(1 << Snimac_DHT22);
_delay_ms(2);
PORTB |= (1 << Snimac_DHT22);
_delay_us(30);
DDRB &= ~(1 << Snimac_DHT22);
// Čakanie na odpoveď
uint16_t timeout;
timeout = 100;
while ((PINB & (1 << Snimac_DHT22)) && timeout--) _delay_us(1);
if (timeout == 0) return uart_puts("Timeout LOW\n"), 0;
timeout = 100;
while (!(PINB & (1 << Snimac_DHT22)) && timeout--) _delay_us(1);
if (timeout == 0) return uart_puts("Timeout HIGH\n"), 0;
timeout = 100;
while ((PINB & (1 << Snimac_DHT22)) && timeout--) _delay_us(1);
if (timeout == 0) return uart_puts("Timeout koniec HIGH\n"), 0;
// Príjem dát (5*8 = 40 bitov)
for (j = 0; j < 5; j++) {
for (i = 0; i < 8; i++) {
timeout = 100;
while (!(PINB & (1 << Snimac_DHT22)) && timeout--) _delay_us(1);
if (timeout == 0) return uart_puts("Timeout bit start\n"), 0;
uint8_t count = 0;
while ((PINB & (1 << Snimac_DHT22))) {
_delay_us(1);
if (++count > 100) break;
}
if (count > 40) bity[j] |= (1 << (7 - i));
}
}
// Kontrola prijatých dát (Posledný bajt je súčtom 4 pred ním)
if ((uint8_t)(bity[0] + bity[1] + bity[2] + bity[3]) != bity[4]) {
uart_puts("Chyba: Kontrolny sucet\n");
return 0;
}
// Uloženie hodnôt
for (i = 0; i < 5; i++) Pole_udajov[i] = bity[i];
return 1;
}
// ======================[ Výpis float hodnoty na LCD ]======================
void Vypis_na_displej(float hodnota) {
// Toto je všetko len formátovanie aby sa to vypísalo na displej "pekne"
// Rozdelenie na celé čísla a čísla za desatinnou čiarkou (DHT22 má rozlíšenie len na jedno desatinné miesto)
int jednotky = (int)hodnota;
int desatiny = (int)((hodnota - jednotky) * 10);
// V prípade že by teplota bola záporná (Nemal som ako vyskúšať takže neviem či je to úplne správne)
if (hodnota < 0) {
lcd_data('-');
jednotky = -jednotky;
desatiny = -desatiny;
}
// Vypísanie jednotiek
if (jednotky >= 10)
lcd_data('0' + (jednotky / 10));
else
lcd_data('0');
// Vypísanie desatín
lcd_data('0' + (jednotky % 10));
lcd_data('.');
lcd_data('0' + (desatiny % 10));
}
// ======================[ Vlastné znaky pre LCD ]======================
void Vlastne_znaky(void) {
unsigned char znak_stupen[8] = {0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00, 0x00}; // Vytvorenie vlastného znaku "°" pomocou jednotlivých pixelov (Samotný displej je síce 5*7 pixelov no mal som problém s kompiláciou a preto je všade 8 hodnôt (tvárim sa že je jeden znak je 5*8))
unsigned char znak_t_makcen[8] = {0x0D, 0x0A, 0x1C, 0x08, 0x08, 0x09, 0x06, 0x00}; // Vytvorenie vlastného znaku "ť"
// Uložím znaky aby si ich pamätal displej
def_znak(znak_stupen, 1);
def_znak(znak_t_makcen, 2);
}
#include <avr/io.h>
//#include <util/setbaud.h>
#define BAUD 9600
#define F_CPU 16000000UL
void uart_init( void )
{
UBRR0 =103;
/*
#if USE_2X
UCSR0A |= _BV(U2X0);
#else
UCSR0A &= ~(_BV(U2X0));
#endif*/
UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-bit data */
UCSR0B = _BV(RXEN0) | _BV(TXEN0); /* Enable RX and TX */
}
void uart_putc(char c)
{
if (c == '\n')
{
uart_putc('\r');
}
loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */
UDR0 = c;
}
void uart_puts(const char *s)
{
while(*s!='\0'){
uart_putc(*s);
s++;
}
}
char uart_getc(void) {
loop_until_bit_is_set(UCSR0A, RXC0); /* Wait until data exists. */
return UDR0;
}
#ifndef UART_H_
#define UART_H_
#define BAUD 9600
#define F_CPU 16000000UL
void uart_init( void );
void uart_putc( char c );
void uart_puts( const char *s );
char uart_getc( void );
#endif
#include "lcd_ch.h"
unsigned char Znak_OO[8]= {0x00,0x00,0x00,0x00,0x00,0x00,0x15,0};// Prazdny znak dole su tri bodky
unsigned char Znak_SC[8]= {0x1A,0x1D,0x04,0x04,0x04,0x05,0x02,0};// st.C
unsigned char Znak_SM[8]= {0x0A,0x04,0x0E,0x10,0x0E,0x01,0x1E,0};// s + mekcen
unsigned char Znak_CM[8]= {0x0A,0x04,0x0E,0x10,0x10,0x11,0x0C,0};// c + mekcen
// Vynulovanie CGRAM
void def_Clear_spec_znaky(void){
// zapis specialneho znaku do CGRAM na poziciu 0, a 7
for(char i = 0; i < 8; i++) def_znak( Znak_OO,i);
lcd_command(0x80);
}
void def_spec_znaky(void){
// zapis vlastnych znakkov do CGRAM na poziciu 4, a 0 (8)
def_znak( Znak_SC,4);// stupen Celzia
def_znak( Znak_SM,0);// s + makcen
// obnovenie obsahu AC. AC nastvime na 1. riadok, 1. znak
lcd_command(0x80);
}
#ifndef _Shield_LCD
void def_spec_znaky_AC(void){
// zapis specialneho znaku do CGRAM na poziciu 7
unsigned char obsah_AC = 0;
obsah_AC = lcd_read_AC(); //
// kon_vyp = obsah_AC;
// sem dame nove vlastne znaky
def_znak( Znak_CM,7);// c + makcen
// obnovenie obsahu AC. AC
lcd_command(0x80|obsah_AC);
}
#endif
void ini_ports(void){
/* inicializacia portov - vstupy / vystupy
oba typy displaja */
// nasledovna # riadky su spolocne pre oba typy LCD
LCD_CTRL_DDR |= (1<<LCD_EN_pin); // (Enable) output
LCD_CTRL_DDR |= (1<<LCD_RS_pin); // (RS) output
LCD_DATA_DDR |= (1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin); // Datove piny ako Output (Data pre display)
#ifndef _Shield_LCD
// klasicky LCD vyuziva aj RW pin
LCD_CTRL_DDR |= (1<<LCD_RW_pin); // (RW) output
#endif
}
void En_imp(void) {
LCD_CTRL_PORT |= (1<<LCD_EN_pin); // -> "log.1"
LCD_DELAY; // cca 400 ns
LCD_CTRL_PORT &= ~(1<<LCD_EN_pin); // -> "log.0" spolu cca 500 ns
}
void lcd_init(void)
{ // 4 bitove pripojenie display-a
ini_ports(); // inicializacia porov
_delay_ms(15);
// 1. -------------------
LCD_CTRL_PORT &= ~(1 << LCD_RS_pin); // set RS = to "log. 0" Instruction Register (IR)
#ifndef _Shield_LCD
LCD_CTRL_PORT &= ~(1 << LCD_RW_pin) ; // set R/W = to "log. 0" - Write
#endif
// RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
// 0 0 0 0 1 1 x x x x = 0x30
PORT_DATA_WR_H(0x30); // 8-bitove pripojenie
En_imp();
// 2. -------------------
// zopakujem 8-bitove pripojenie
_delay_ms(5); En_imp();
// 3. -------------------
// zopakujem 8-bitove pripojenie
_delay_ms(1); En_imp(); // - stacilo by delay 0.1ms, momentalne najkratsie nastavitelne je 1ms
// 4. -------------------
// zmenim na 4-bitove pripojenie
// RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
// 0 0 0 0 1 0 x x x x = 0x20
PORT_DATA_WR_H(0x20); // 4-bitove pripojenie
_delay_ms(1); En_imp(); // - stacilo by delay 0.04ms
// -------------------
// LCD function set mod: DL = 0 - 4-bitove data, N = 1 - 2 riadky, F = 0 - 5x7 dots
// RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
// 0 0 0 0 1 DL N F x x = 0x28
// Mozeme nasledujuci prikaz pre LCD vynechat?
_delay_ms(2);
lcd_command(0x28);
// RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
// 0 0 0 0 0 0 0 0 0 1 = 0x01 , Display clear
// Mozeme nasledujuci prikaz pre LCD vynechat?
_delay_ms(2);
lcd_command(0x01);
// RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
// 0 0 0 0 0 0 0 1 I/D S = 0x06, I/D = 1 - inkrement
// Mozeme nasledujuci prikaz pre LCD vynechat?
_delay_ms(2);
lcd_command(0x02);
// RS R/W DB7 DB6 DB5 DB4 DB3 DB2 DB1 DB0
// 0 0 0 0 0 0 1 D C B = 0x0E, D = C = 1 - Display a kurzor zapnute
// Mozeme nasledujuci prikaz pre LCD vynechat?
_delay_ms(2);
lcd_command(0x0E); // B = 0 - blikanie vypnute
}
//zapis data do Data Register
void lcd_data(unsigned char data){
while(busy_flag() & 0x80);
//while(rd_BF()); // test BF sa da realizovat pre klasicky LCD, nie Shield
LCD_CTRL_PORT |= (1<<LCD_RS_pin); // (RS = High)
#ifndef _Shield_LCD
LCD_CTRL_PORT &= ~(1<<LCD_RW_pin); // (RW = Low, write)
#endif
wr_data (data);
}
// zapis commandu do Instruction Register
void lcd_command(unsigned char command){
while(busy_flag() & 0x80);
//while(rd_BF()); // test BF sa da realizovat pre klasicky LCD, nie Shield
LCD_CTRL_PORT &= ~(1<<LCD_RS_pin); // (RS = Low)
#ifndef _Shield_LCD
LCD_CTRL_PORT &= ~(1<<LCD_RW_pin); // (RW = Low, write)
#endif
wr_data (command);
}
void wr_data(unsigned char data) {
PORT_DATA_WR_H(data); // data High nibble
En_imp();
PORT_DATA_WR_L(data); // data Low nibble
En_imp();
}
#ifdef _Shield_LCD
// namiesto testu BF "pockam".
// LCD typu Shield ma WR pripojene na GND
unsigned char busy_flag(void){
_delay_ms(2);
return(0);
}
#else
// namiesto testu BF "pockam". Vacsine prikazov tento cas vyhovuje
/*int busy_flag(void){
_delay_ms(2);
return(0);
}
*/
// klasicke pripojenie LCD umozni aj test BF
unsigned char busy_flag(void){ // rd_BF
unsigned char pom = 0;
LCD_CTRL_PORT &= ~(1<<LCD_RS_pin); // (RS = Low)
LCD_CTRL_PORT |= (1<<LCD_RW_pin); // (RW = High, read)
// port B, datove bity budu teraz input
LCD_DATA_DDR &= ~((1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin)); // Datove piny nastavime na input, (Data pre disp)
// Spravne by sa malo vycitavaj pred dobeznou hranou EN impulzu. Je tam urcity presah. Mozeme vycitat aj po dobeznej hrane.
En_imp();
pom = PORT_DATA_RD_H; // vycitam High nibble AC
En_imp();
pom |= PORT_DATA_RD_L; // vycitam Low nibble AC
// datove bity zase output
LCD_DATA_DDR |= (1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin); // Datove piny nastavime na output (Data pre disp)
//if(pom & 0x80) return 1; // display je busy
//else
return pom; // display je not busy
}
#endif
void zob_text(char *s){
register unsigned char c;
while((c = *s++)) lcd_data(c); // retazec konci "nulou"
}
void def_znak(unsigned char *ZnakArray,unsigned char kam) {
lcd_command(0x40|(kam << 3)); //nastavenie adresy znaku v CGRAM
for(unsigned char i = 0; i < 8; i++) lcd_data( *(ZnakArray + i));
}
/* ******************************************************** */
/* vypis retazca na poziciu, resp. podla nasledovnych */
/* formatovacich prikazov */
/* : \r - prechod na novy riadok */
/* : \f - prechod na zaciatok displeja */
/* ******************************************************** */
void lcd_puts( const char *s) /* definicia funkcie */
{
}
#ifndef _Shield_LCD
int8_t lcd_read_AC(void){ // rd_BF
char pom_AC ;
while((pom_AC = busy_flag( )) & 0x80);
// kedze po BF = 0 este cca 4us sa nezmenil obsah AC
// treba vycitat este raz
pom_AC = busy_flag( );
return pom_AC; // display not busy
}
#endif
#ifndef F_CPU
#define F_CPU 16000000UL /* Define CPU frequency here 16MHz */
#endif
#ifndef LCD_CH_H_
#define LCD_CH_H_
#include <avr/io.h>
#include <util/delay.h>
/* Nasledovne define rozhodne ktora cast programu sa prelozi:
LCD Shiled (WR ma pripojene na GND) a
priradenie pinov je dane napr.:
"zamrzne"
- https://www.14core.com/wiring-the-lcd-16x2-keypad-shield-on-arduino/
- https://wiki.dfrobot.com/LCD_KeyPad_Shield_For_Arduino_SKU__DFR0009
resp.
LCD, zapojenie vid. stranka MIPS
"zamrzne"
- http://senzor.robotika.sk/sensorwiki/index.php/LCD_displej_s_radi%C4%8Dom_HD44780
, ktore ma pripojene aj pin WR
t.j. moze sa testovat aj pin BF
*/
/* !!!!!!!
#define _Shield_LCD
!!!!!!! */
extern unsigned char kon_vyp;
#ifdef _Shield_LCD
#define LCD_CTRL_DDR DDRB
#define LCD_CTRL_PORT PORTB
#define LCD_DATA_DDR DDRD
#define LCD_DATA_PORT PORTD
// Riadiaca zbernica display-a
#define LCD_RS_pin 0
#define LCD_RW_pin = 0
#define LCD_EN_pin 1
// Datova zbernica
#define LCD_D4_pin 4
#define LCD_D5_pin 5
#define LCD_D6_pin 6
#define LCD_D7_pin 7
#else
// LCD klasik yapojenie vid. MIPS
#define LCD_CTRL_DDR DDRD
#define LCD_CTRL_PORT PORTD
#define LCD_DATA_DDR DDRB
#define LCD_DATA_PORT PORTB
#define LCD_DATA_PIN PINB
#define LCD_RS_pin 2
#define LCD_RW_pin 3
#define LCD_EN_pin 4
#define LCD_D4_pin 1
#define LCD_D5_pin 2
#define LCD_D6_pin 3
#define LCD_D7_pin 4
#endif
// Oneskorenie 6 SC
#define NOP() asm("nop")
#define LCD_DELAY NOP();NOP();NOP();NOP();NOP();NOP();
#ifdef _Shield_LCD
// formatovanie dat
#define PORT_DATA_WR_H(x) LCD_DATA_PORT &=0b00001111; LCD_DATA_PORT |= (x & 0xF0 )
#define PORT_DATA_WR_L(x) LCD_DATA_PORT &=0b00001111; LCD_DATA_PORT |= (x & 0x0F )<<4
#else
#define PORT_DATA_WR_H(x) LCD_DATA_PORT &=0b11100001; LCD_DATA_PORT |= (x & 0xF0 )>>3
#define PORT_DATA_WR_L(x) LCD_DATA_PORT &=0b11100001; LCD_DATA_PORT |= (x & 0x0F )<<1
#define PORT_DATA_RD_H ((LCD_DATA_PIN & ((1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin)))<<3)
#define PORT_DATA_RD_L ((LCD_DATA_PIN & ((1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin)))>>1)
#endif
/* Public functions for your use */
#ifndef _Shield_LCD
int8_t lcd_read_AC(void);
void def_spec_znaky_AC(void);
#endif
void lcd_init(void);
void lcd_data(unsigned char );
void lcd_command(unsigned char );
// void lcd_puts(const char *s); /* deklaracia funkcie */
void ini_ports(void);
void En_imp(void);
void wr_data (unsigned char );
unsigned char busy_flag(void);
void zob_text(char *);
void def_Clear_spec_znaky(void);
void def_znak(unsigned char *,unsigned char );
void def_spec_znaky(void);
#endif /* LCD_CH_H_ */
Kompletný projekt nájdete tu:
Zdrojový kód: ProjektMartinHubocky.zip
Overenie funkčnosti zariadenia
Funkčnosť zariadenia som overil veľmi jednoducho a to tak že som menil teplotu a vlhkosť v okolí snímača a sledoval či sa zariadenie správa tak ako som chcel. Vlhkosť som menil tak že som na snímač dýchal ústami čím sa mi podarilo zvýšiť vlhkosť niekedy až na 90%. Účinok je takmer instantný a rovnako tak je aj pokles naspäť veľmi rýchly. No a ako som menil teplotu? (.....) Bohužiaľ ma nenapadlo nič "inteligentnejšie" ako ohrievať snímač zospodu zapaľovačom. Bolo to pomerne rýchle, no ale samozrejme som svoju "šikovnosť" nezaprel a raz sa mi podarilo snímač "opáliť". Našťastie stále funguje...
Na obrázku číslo 5 môžete vidieť funkčnosť UART-u a nižšie je video kde môžete vidieť priebeh testovania zariadenia. Rád by som podotkol že na videu sú farby RGB LED diód veľmi skreslené a ťažko ich od seba odlíšiť (niekedy sa zdá že sa farby ani nezmenili), za čo sa ospravedlňujem.


Video:
Literatúra a použité zdroje
Dokumentácia k mikrokontroléru:
- Mikrokontrolér Arduino UNO R3 - PINOUT
- Mikrokontrolér Arduino UNO R3 - DATASHEET
- Mikrokontrolér Arduino UNO R3 - SCHEMATICS
- Dokumentácia k procesoru ATmega328P
Dokumentácia k snímaču DHT22:
Dokumentácia k LCD displeju: