Operácie

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.

Vývojová doska ACROB.

Literatúra:


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

Senzor DHT11 – pohľad zhora

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.

LCD displej s ovládačom ST7036

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

Schéma zapojenia vytvorená v programe EasyEDA.


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.

Fotka hotového zariadenia

Video:



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