Kombinovaný snímač teploty a vlhkosti DHT11
Zo stránky SensorWiki
Záverečný projekt predmetu MIPS / LS2025 - Aleh Sobaleu
Zadanie
Cieľom tohto projektu bolo navrhnúť jednoduchý systém na meranie teploty a vlhkosti pomocou senzora DHT11, ktorý komunikuje s mikrokontrolérom ATmega328P. Získané údaje sa zobrazujú na LCD displeji, pričom celý proces prebieha v reálnom čase bez potreby ďalšieho zásahu používateľa.

Literatúra:
- Dokumentácia k doske Acrob
- Dokumentácia k senzoru teploty a vlhkosti DHT11
- Dokumentácia k ST7036 LCD Controller
Analýza a opis riešenia
Projekt som postavil na základnej doske s mikrokontrolérom ATmega328P, pričom k nej boli pripojené dva hlavné komponenty: DHT11 senzor a LCD displej. Celé zapojenie som najskôr testoval na breadboarde a až po overení funkčnosti som ho zafixoval.
Použité súčiastky:
1. DHT11 – senzor teploty a vlhkosti Tento senzor meria aktuálnu teplotu (v stupňoch Celzia) a relatívnu vlhkosť vzduchu v percentách. Má 3 piny:
VCC – napájanie (5V)
GND – zem
DATA – dátový výstup
Senzor komunikuje cez digitálny jednovodičový protokol, takže na prenos údajov je použitý iba jeden pin mikrokontroléra – v mojom prípade to bol PD5 (Arduino pin D5).

2. LCD displej (EA DOGM163 / ST7036) Použil som trojriadkový LCD displej s ovládačom ST7036, ktorý komunikuje cez SPI zbernicu. Pripojenie vyzerá nasledovne:
SI → PB3
SCK → PB5
RS → PD2
CS → PD4
BKLT → PD6
+5V a GND napájanie
LCD displej slúži na prehľadné zobrazenie aktuálnej teploty a vlhkosti. Dáta sa obnovujú každé dve sekundy.

3. Schéma zapojenia:
Celé zariadenie bolo zapojené na breadboarde pomocou prepojovacích vodičov. Použil som mikrokontrolér ATmega328P, ktorý riadi čítanie dát zo senzora DHT11 a ich následné zobrazenie na LCD displeji. Komponenty boli zvolené tak, aby spolu komunikovali spoľahlivo bez rušenia a aby sa dali pripojiť priamo k pinom mikrokontroléra bez potreby zložitej logiky.
Základné zapojenie komponentov:
1. Senzor DHT11
VCC senzora → 5V na doske
GND senzora → GND na doske
DATA senzora → pin PD5 (Arduino D5)
Senzor DHT11 komunikuje cez digitálny jednovodičový protokol. Mikrokontrolér najprv vyšle signál na prebudiť senzor a potom čaká na odpoveď a číta 5 bajtov – vlhkosť, teplotu a kontrolný súčet.
2. LCD displej (ST7036)
LCD displej komunikuje cez SPI.
Použité piny:
PB3 (MOSI) → SI (dáta do displeja)
PB5 (SCK) → hodiny (clock)
PD2 → RS (výber dátového alebo príkazového režimu)
PD4 → CS (výber displeja)
PD6 → podsvietenie (BKLT)
VCC → 5V
GND → GND
Displej zobrazuje na 1. riadku teplotu v °C a na 2. riadku vlhkosť v %.
UART výstup je pripojený k USB-UART prevodníku a slúži na monitorovanie výstupu cez terminál (PuTTY).

Algoritmus a program
Použité knižnice:
dht11.h a dht11.c – vlastná knižnica, ktorú som upravil pre čítanie dát zo senzora DHT11 cez digitálny pin. Obsahuje základnú komunikáciu, spracovanie bitov a kontrolu správnosti pomocou kontrolného súčtu.
lcd.h a lcd.c – knižnica pre prácu s LCD displejom, ktorú sme dostali ako základ na cvičení. Rozšíril som ju o vlastné funkcie lcd_puts, lcd_setCursor, lcd_clearline a lcd_print_num.
uart.h a uart.c – jednoduchá knižnica na výpis textu cez sériový port. Umožňuje používať štandardné funkcie ako printf() a uart_puts().
#include "lcd.h" // Knižnica pre prácu s LCD displejom
#include <avr/io.h> // Základné definície pre AVR
#include <util/delay.h> // Funkcia pre oneskorenie
#include <stdlib.h>
#include <stdio.h>
#include "uart.h" // Knižnica pre sériovú komunikáciu
#include "dht11.h" // Knižnica pre DHT11 senzor
#define LED1 PB5 // Interná LED na doske (napr. Arduino UNO)
#define LED2 PC2 // Externá LED (voliteľná)
#define SW_A PC3 // Tlačidlo (nepoužíva sa v tomto kóde)
FILE mystdout = FDEV_SETUP_STREAM(uart_putc, NULL, _FDEV_SETUP_WRITE); // Pripojenie výstupu printf k UARTu
// Pomocná funkcia na zobrazenie čísla na LCD ako text
void lcd_print_num(uint8_t num) {
char buffer[5];
itoa(num, buffer, 10); // Konverzia čísla na string
lcd_puts(buffer); // Zobrazenie na LCD
}
int main(void) {
uint8_t temp, hum; // Premenné pre teplotu a vlhkosť
// Nastavenie pinov ako výstupy pre LEDky
DDRB |= (1<<LED1);
DDRC |= (1<<LED2);
uart_init(); // Inicializácia sériového portu
stdout = &mystdout; // Prepojenie printf na UART
printf("DHT11 + LCD init\n");
lcd_init(); // Inicializácia LCD
lcd_bklt(1); // Zapnutie podsvietenia
lcd_command(0x0C); // Zapnutie displeja, vypnutie kurzora
lcd_clear(); // Vyčistenie obrazovky
while (1) {
// Pokus o načítanie dát zo senzora
if (dht11_read(&temp, &hum) == 0) {
// Ak úspešné čítanie — spracovanie teploty
char send[16];
sprintf(send,"Teplota: %d %C",temp); // Formátovaný výstup
lcd_setCursor(0, 0); // Riadok 1, stĺpec 0
lcd_puts(send);
lcd_putc(0xDF); // Znak ° (stupne)
lcd_putc('C'); // Celsia
// Spracovanie vlhkosti
char message[16];
sprintf(message,"Vlhkost: %d %%",hum);
lcd_setCursor(1, 0); // Riadok 2, stĺpec 0
lcd_puts(message);
// Výpis aj do terminálu
printf("T: %d C, H: %d %%\n", temp, hum);
} else {
// Ak čítanie zlyhalo — chyba
lcd_setCursor(0, 0);
lcd_puts("Chyba citania "); // Hlásenie na LCD
lcd_setCursor(1, 0);
lcd_puts(" "); // Vymazanie druhého riadku
printf("Read error: %d\n", dht11_read(&temp, &hum));
}
_delay_ms(2000); // Požadovaná pauza medzi čítaniami
}
}
#define LED PB5 // interná LED na doske (napr. Arduino UNO)
/* Užitočné makrá pre prácu s tlačidlami alebo pinmi */
// Test, či je bit nastavený
// bit_is_set(PINB, SW1)
// Test, či je bit nulový (tlačidlo stlačené pri pull-up odpore)
// bit_is_clear(PINB, SW1)
/* Preddefinované slučky pre čakanie na udalosť */
// Čakanie, kým sa tlačidlo pustí
// loop_until_bit_is_set(PINB, SW1);
// Čakanie, kým sa tlačidlo stlačí
// loop_until_bit_is_clear(PINB, SW1);
#define set_bit(ADDRESS,BIT) (ADDRESS |= (1<<BIT)) // Nastaví bit v registri
#define clear_bit(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT)) // Vynuluje bit v registri
#ifndef UART_H_
#define UART_H_
#include <stdio.h>
/* Výpočet predvoľby pre BAUD rate na základe frekvencie CPU a požadovanej rýchlosti.
* Vzorec je prevzatý z datasheetu pre ATmega328P.
*/
#define BAUD_PRESCALE (((F_CPU / (BAUDRATE * 16UL))) - 1)
/* Inicializuje potrebný hardvér (voliteľné, ak sa používa) */
void hw_init(void);
/* Inicializuje UART (nastaví rýchlosť, formát rámca a povolí prenos) */
void uart_init(void);
/*
* Funkcia pre výpis znakov cez UART, kompatibilná s printf().
* Vďaka tomu je možné napísať:
* printf("Text %d\n", hodnota);
* a výstup sa odošle cez UART.
*/
int uart_putc(char c, FILE *stream);
/* Jednoduchý výpis reťazca cez UART (bez formátovania) */
void uart_puts(const char *s);
/* Prijme jeden znak z UART (blokuje, kým nepríde znak) */
char uart_getc(void);
/* Jednoduchá funkcia delay (ak sa nepoužíva _delay_ms()) */
void delay(int delay);
#endif /* UART_H_ */
/* ************************************************************************* */
/* FileName: uart.c */
/* ************************************************************************* */
#include <avr/io.h>
#include <util/delay.h>
#include "uart.h"
void hw_init( void )
{
DDRB |= (1<<LED); // PORTB.5 kde je LED ma byt OUTPUT
/* sem si mozete dopisat svoje vlastne inicializacne prikazy */
}
void uart_init( void )
{
// for different BAUD rate change the project settings, or uncomment
// following two lines:
// #undef BAUD // avoid compiler warning
// #define BAUD 115200
#include <util/setbaud.h> // requires defined BAUD
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
#if USE_2X // defined in setbaud.h
UCSR0A |= (1 << U2X0);
#else
UCSR0A &= ~(1 << U2X0);
#endif
UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); /* 8-bit data */
UCSR0B = _BV(RXEN0) | _BV(TXEN0); /* Enable RX and TX */
}
int uart_putc( char c, FILE *stream )
{
if (c == '\n')
uart_putc('\r',stream);
loop_until_bit_is_set(UCSR0A, UDRE0); /* Wait until data register empty. */
UDR0 = c;
return 0;
}
void uart_puts(const char *s)
{
/* toto je vasa uloha */
}
char uart_getc(void)
{
loop_until_bit_is_set(UCSR0A, RXC0); /* Wait until data exists. */
return UDR0;
}
void delay(int delay) // vlastna funkcia pre dlhsie casy
{
for (int i=1; i<=delay; i++)
_delay_ms(1);
}
/*
* lcd.h Library for MISA @ FEI STU
*
* Created: 15. 4. 2025 20:48:48 - added char definition support, removed ext. dependencies
* Author: Richard Balogh based on gitHub code for EA*DOGM163
*/
#ifndef LCD_H_
#define LCD_H_
#include <stdlib.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#define CHARACTER_BUFFER_BASE_ADDRESS 0x80
#define CHARACTERS_PER_ROW 16
#define DISPLAY_RS_PORT PORTD
#define DISPLAY_RS_DDR DDRD
#define DISPLAY_RS_PIN 2
#define DISPLAY_CSB_PORT PORTD
#define DISPLAY_CSB_DDR DDRD
#define DISPLAY_CSB_PIN 4
#define DISPLAY_BKLT_PORT PORTD
#define DISPLAY_BKLT_DDR DDRD
#define DISPLAY_BKLT_PIN 6
#define DISPLAY_RS_low (DISPLAY_RS_PORT &= ~(1<<DISPLAY_RS_PIN))
#define DISPLAY_RS_high (DISPLAY_RS_PORT |= (1<<DISPLAY_RS_PIN))
#define DISPLAY_CSB_low (DISPLAY_CSB_PORT &= ~(1<<DISPLAY_CSB_PIN))
#define DISPLAY_CSB_high (DISPLAY_CSB_PORT |= (1<<DISPLAY_CSB_PIN))
#define DISPLAY_BKLT_low (DISPLAY_BKLT_PORT &= ~(1<<DISPLAY_BKLT_PIN))
#define DISPLAY_BKLT_high (DISPLAY_BKLT_PORT |= (1<<DISPLAY_BKLT_PIN))
//instructions (see the ST7036 instruction set for further information)
#define INSTRUCTION_CLEAR_DISPLAY 0b00000001
#define INSTRUCTION_FUNCTION_SET_INIT_0 0b00110011
#define INSTRUCTION_FUNCTION_SET_INIT_1 0b00110010
#define INSTRUCTION_FUNCTION_SET_INIT_2 0b00101001
#define INSTRUCTION_INSTRUCTION_SET_0 0b00101000
#define INSTRUCTION_INSTRUCTION_SET_1 0b00101001
#define INSTRUCTION_BIAS_SET 0b00010101
#define INSTRUCTION_POWER_CONTROL 0b01010011
#define INSTRUCTION_FOLLOWER_CONTROL 0b01101100
#define INSTRUCTION_CONTRAST_SET 0b01111111
#define INSTRUCTION_DISPLAY_ON 0b00001100
#define INSTRUCTION_ENTRY_MODE 0b00000110
void lcd_init(void);
void lcd_write( char data );
void lcd_data( char data );
void lcd_command( char instruction );
void lcd_putc( char znak );
void lcd_puts( char *string );
void lcd_clear( void );
void lcd_clearline( unsigned char zeile );
void lcd_setCursor(char row, char col);
void lcd_bklt( char OnOff);
void def_znak(unsigned char *ZnakArray, unsigned char Address);
#endif /* LCD_H_ */
#include "lcd.h"
/* Funkcia zapise jeden bajt po SPI zbernici do zariadenia */
void lcd_write(char data) {
signed char index = 8;
char c_data;
DISPLAY_CSB_PORT &= ~(1 << DISPLAY_CSB_PIN); // Chip-Select do log.0
c_data = data;
do {
_delay_us(6);
if (c_data & 0x80)
PORTB |= (1 << 3); // MOSI
else
PORTB &= ~(1 << 3);
_delay_us(5);
PORTB &= ~(1 << 5); // CLK low
_delay_us(6);
PORTB |= (1 << 5); // CLK high
c_data <<= 1;
index--;
} while (index > 0);
_delay_ms(2);
DISPLAY_CSB_PORT |= (1 << DISPLAY_CSB_PIN); // CS high
}
void lcd_command(char instruction) {
DISPLAY_RS_PORT &= ~(1 << DISPLAY_RS_PIN);
_delay_us(1);
lcd_write(instruction);
}
void lcd_data(char data) {
DISPLAY_RS_PORT |= (1 << DISPLAY_RS_PIN);
_delay_us(7);
lcd_write(data);
}
void lcd_init(void) {
DDRB |= (1 << PB3) | (1 << PB5); // MOSI + SCK
DISPLAY_RS_DDR |= (1 << DISPLAY_RS_PIN);
DISPLAY_CSB_DDR |= (1 << DISPLAY_CSB_PIN);
DISPLAY_BKLT_DDR |= (1 << DISPLAY_BKLT_PIN);
PORTB |= (1 << PB5);
DISPLAY_CSB_PORT |= (1 << DISPLAY_CSB_PIN);
DISPLAY_RS_PORT &= ~(1 << DISPLAY_RS_PIN);
_delay_ms(50);
lcd_command(0x39);
_delay_us(50);
lcd_command(0x1D);
_delay_us(50);
lcd_command(0x50);
_delay_us(50);
lcd_command(0x6C);
_delay_ms(500);
lcd_command(0x7C);
_delay_us(50);
lcd_command(0x38);
_delay_us(50);
lcd_command(0x0F);
_delay_us(50);
lcd_command(0x01);
_delay_ms(400);
lcd_command(0x06);
_delay_us(50);
}
void lcd_putc(char znak) {
lcd_data(znak);
}
void lcd_puts(char *string) {
while (*string) {
lcd_data(*string++);
}
}
void lcd_setCursor(char row, char col) {
char address;
switch (row) {
case 0: address = 0x00 + col; break;
case 1: address = 0x10 + col; break;
case 2: address = 0x20 + col; break;
default: address = 0x00 + col; break;
}
lcd_command(0x80 | address);
}
void lcd_clearline(unsigned char riadok) {
lcd_setCursor(riadok, 0);
for (unsigned char i = 0; i < 16; i++) {
lcd_data(' ');
}
}
void lcd_clear(void) {
lcd_clearline(0);
lcd_clearline(1);
lcd_clearline(2);
}
void lcd_bklt(char OnOff) {
if (OnOff)
DISPLAY_BKLT_PORT |= (1 << DISPLAY_BKLT_PIN);
else
DISPLAY_BKLT_PORT &= ~(1 << DISPLAY_BKLT_PIN);
}
void def_znak(unsigned char *ZnakArray, unsigned char Address) {
lcd_command(0x40 | (Address << 3));
for (unsigned char i = 0; i < 8; i++) {
lcd_data(*(ZnakArray + i));
}
lcd_command(0x80);
}
#ifndef DHT11_H_
#define DHT11_H_
#include <avr/io.h>
#include <util/delay.h>
#define DHT11_DDR DDRD
#define DHT11_PORT PORTD
#define DHT11_PIN PIND
#define DHT11_INPUTPIN PD5
uint8_t dht11_read(uint8_t* temperature, uint8_t* humidity);
#endif
#include "dht11.h"
uint8_t dht11_read(uint8_t* temperature, uint8_t* humidity) {
uint8_t bits[5] = {0};
uint8_t i, j = 0;
DHT11_DDR |= (1 << DHT11_INPUTPIN);
DHT11_PORT &= ~(1 << DHT11_INPUTPIN);
_delay_ms(18);
DHT11_PORT |= (1 << DHT11_INPUTPIN);
_delay_us(40);
DHT11_DDR &= ~(1 << DHT11_INPUTPIN);
// if ((DHT11_PIN & (1 << DHT11_INPUTPIN))) return 1;
_delay_us(80);
// if (!(DHT11_PIN & (1 << DHT11_INPUTPIN))) return 2;
_delay_us(80);
for (j = 0; j < 5; j++) {
for (i = 0; i < 8; i++) {
while (!(DHT11_PIN & (1 << DHT11_INPUTPIN)));
_delay_us(30);
if (DHT11_PIN & (1 << DHT11_INPUTPIN)) bits[j] |= (1 << (7 - i));
while (DHT11_PIN & (1 << DHT11_INPUTPIN));
}
}
if ((uint8_t)(bits[0] + bits[1] + bits[2] + bits[3]) != bits[4])
return 3;
*humidity = bits[0];
*temperature = bits[2];
return 0;
}
Kompletný projekt nájdete tu:
Zdrojový kód: ProjektAlehSobaleu.zip
Overenie
Správnosť zapojenia a funkčnosť programu som overoval priamo počas vývoja. Aby som otestoval, či senzor DHT11 správne reaguje na zmeny prostredia, rozhodol som sa použiť jednoduchú a okamžite účinnú metódu – fúkaním ústami na senzor.
Teplý a vlhký vzduch z dychu spôsobil, že senzor zaznamenal nárast teploty aj relatívnej vlhkosti. Tieto zmeny boli následne zobrazené na LCD displeji, čím som si overil, že čítanie údajov prebieha korektne a výstup je spoľahlivý.
Hodnoty sa obnovovali každé dve sekundy, takže reakcia bola viditeľná veľmi rýchlo. Údaje sa zároveň zobrazovali aj v sériovom termináli cez UART, čo uľahčilo ladenie a sledovanie výsledkov.

Video:
Kľúčové slová 'Category', ktoré sú na konci stránky nemeňte.