Как использовать датчик температуры и влажности воздуха DHT11 с PIC16F628A и LCD

Добавлено29 октября 2016 в 14:00

В данной статье мы рассмотрим, как, используя PIC-контроллер, можно считать из DHT11 значения температуры и относительной влажности воздуха и показать их на LCD. В этом примере мы будем использовать микроконтроллер PIC16F628A.

Нам потребуется

Чтобы выполнить этот проект вам понадобится следующее:

Введение

DHT11 – это датчик температуры и влажности воздух, который использует один провод для передачи 40 бит данных. Первые 16 бит – это целая и дробная части значения влажности, следующие 16 бит – целая и дробная части значения температуры, и последние 8 бит – это контрольная сумма.

Чтобы DHT11 и микроконтроллер начали общаться друг с другом, их необходимо засинхронизировать. Чтобы засинхронизировать их, микроконтроллер посылает сигнал старта, который представляет собой импульс длительностью 20 мкс на выводе данных. После импульса, микроконтроллер ждет получения данных. В программе мы должны изменить направление вывода данных. Вы можете прочитать подробности о DHT11 в техническом описании. У вас может быть датчик, как в 4-выводном, так и в 3-выводном корпусе; но мы используем 3-выводную версию. Между этими версиями нет никакой разницы, а дополнительный вывод ни к чему не подключен.

Датчик температуры и относительной влажности воздуха DHT11
Датчик температуры и относительной влажности воздуха DHT11

Аппаратная часть

Первая умная вещь, которую стоит сделать при создании гаджета, это нарисовать его структурную схему. Это поможет вам увидеть, что вам необходимо, и как это реализовать. Ниже приведена структурная схема нашего термометра.

Структурная схема термометра на PIC16F628, DHT11 и LCD
Структурная схема термометра на PIC16F628, DHT11 и LCD
(может показаться избыточным, составлять структурную схему для каждой мелочи, но это может быть полезно)

Мы хотим, чтобы DHT11 передавал данные микроконтроллеру.

Мы хотим, чтобы микроконтроллер обрабатывал данные и отображал их на LCD дисплее.

Мы хотим, чтобы у нас была возможность прошивать микроконтроллер через ICSP.

Принципиальная схема

Принципиальная схема разделена на блоки.

Принципиальная схема термометра на PIC16F628 и DHT11
Принципиальная схема термометра на PIC16F628 и DHT11
Блок питания
Приводит входное напряжение к значению 5 вольт. Использует стабилизатор напряжения LM7805.
ICSP
Это 5-пиновый разъем, подключенный к выводам программирования микроконтроллера. Мы используем этот разъем для прошивки микроконтроллера.
DHT11
Это 3-пиновый разъем, к которому подключен датчик. Средний вывод подключен к микроконтроллеру для передачи данных.
MCU
Это PIC16F628, который принимает данные от DHT11 и выводит их на LCD дисплей.
Дисплей
LCD дисплей (2 строки по 16 символов), который показывает влажность и температуру воздуха.

Микроконтроллер у нас будет работать от внутреннего тактового генератора с частотой 4 МГц. Поэтому на схеме нам не нужен кварцевый резонатор.

Eagle сгенерировала нам следующий перечень элементов:

Термометр на PIC16F628 и DHT11. Перечень элементов
Позиционное обозначение Номинал Элемент Корпус Библиотека Лист
C1 0.1uF C-EU025-050X050 C025-050X050 rcl 1
C2 100uF CPOL-EUE2.5-5 E2,5-5 rcl 1
C3 0.1uF C-EU025-050X050 C025-050X050 rcl 1
C4 100uF CPOL-EUE2.5-5 E2,5-5 rcl 1
C5 0.1uF C-EU025-050X050 C025-050X050 rcl 1
D1 1N4004 1N4004 DO41-10 diode 1
IC2 7805TV 7805TV TO220V linear 1
IC3 PIC16F628 DIL18 DIL18 ic-package 1
JP1 ICSP PINHD-1X5 1X05 pinhead 1
JP2 16x02 LCD PINHD-1X16 1X16 pinhead 1
JP3 DHT11 PINHD-1X3 1X03 pinhead 1
R1 10K R-EU_0204/7 0204/7 rcl 1
R2 5K TRIM_EU-LI10 LI10 pot 1
R3 4K7 R-EU_0204/7 0204/7 rcl 1
S1 Reset TAC_SWITCHPTH TACTILE-PTH SparkFun 1
X1 7-35vDC W237-10 W237-102 con-wago-500 1

Теперь, когда мы разобрались с аппаратной частью, пришло время заняться программным обеспечением.

Программа

При установке компилятора XC8 вы также добавили и некоторые заголовочные и исходные файлы. В этом руководстве мы используем файлы библиотеки по работе с LCD, которые идут с компилятором XC8: XLCD.H и несколько исходных файлов. Чтобы всё немного упростить, я скопировал содержимое всех исходных файлов в один файл. На моей установке Ubuntu исходные файлы XLCD находятся в каталоге:

/opt/microchip/xc8/v1.34/sources/pic18/plib/XLCD

Там было 10 файлов, bysyxlcd.c, openxlcd.c, putrxlcd.c и т.д.. Я поместил содержимое всех этих файлов в один файл и назвал его my_xlcd.c. Этот файл теперь содержит все функции. Файлы my_xlcd.c и xlcd.h необходимо скопировать в каталог проекта (файл xlcd.h находился в каталоге /opt/microchip/xc8/v1.34/include/plib). Файл xlcd.h – это стандартный файл, который нуждается в небольшом редактировании. Нам необходимо изменить данные о подключении LCD к микроконтроллеру, чтобы они совпадали с нашей схемой:

/* DATA_PORT определяет порт, к которому подключены линии данных LCD */
 #define DATA_PORT      		PORTB
 #define TRIS_DATA_PORT 		TRISB

/* CTRL_PORT определяет порт, к которому подключены линии управления.
 * Это просто для примера, измените их для вашего приложения.
 */
 #define RW_PIN   PORTAbits.RA0   		/* PORT для RW */
 #define TRIS_RW  TRISAbits.TRISA0    	/* TRIS для RW */

 #define RS_PIN   PORTAbits.RA1  		/* PORT для RS */
 #define TRIS_RS  TRISAbits.TRISA1    	/* TRIS для RS */

 #define E_PIN    PORTAbits.RA7 		/* PORT для D  */
 #define TRIS_E   TRISAbits.TRISA7    	/* TRIS для E  */

Теперь связь между LCD дисплеем и микроконтроллером задана. С этими двумя файлами (my_xlcd.h и my_xlcd.c) больше ничего делать не требуется.

Далее основная программа. Она начинается с включения нескольких заголовочных файлов, битов конфигурации, определений, объявления переменных и функций:

// Заголовочные файлы
#include <stdio.h>               // Стандартная библиотека ввода-вывода
#include <stdlib.h>              // Стандартная библиотека функций
#include <xc.h>                  // Библиотека компилятора XC8
#include "my_xlcd.h"             // Наша библиотека LCD 

// Конфигурация
#pragma config FOSC = INTOSCIO  // Биты выбора тактового генератора (внутренний RC генератор: выводы RA6/OSC2/CLKOUT и RA7/OSC1/CLKIN работают, как входы/выходы)
#pragma config WDTE = OFF       // Бит включения сторожевого таймера (WDT выключен)
#pragma config PWRTE = ON       // Бит включения таймера запуска (PWRT включен)
#pragma config MCLRE = ON       // Выбор режима работы вывода RA5/MCLR (вывод RA5/MCLR действует, как MCLR)
#pragma config BOREN = ON       // Бит включения сброса при просадке напряжения питания (сброс BOD включен)
#pragma config LVP = ON         // Бит включения низковольтного программирования (вывод RB4/PGM действует, как PGM, низковольтное программирование включено)
#pragma config CPD = OFF        // Бит защиты данных EEPROM (защита данных выключена)
#pragma config CP = OFF         // Биты защиты кода программы (защита кода программы выключена)

// Определения
#define _XTAL_FREQ 4000000          // Говорим компилятору, что используем 4 МГц
#define data PORTAbits.RA2          // Определение RA0, как вывода данных
#define data_dir TRISAbits.TRISA2   // Определение TRISA0, как порта данных

// Глобальные переменные
char message1[] = "Temp = 00.0 c";
char message2[] = "RH   = 00.0 %";
unsigned short TOUT = 0, CheckSum, i;
unsigned short T_Byte1, T_Byte2, RH_Byte1, RH_Byte2;

// Прототипы функций
void init_XLCD(void);
void DelayFor18TCY(void);
void DelayPORXLCD(void);
void DelayXLCD(void);
void Delay10KTCYx(unsigned char);
void StartSignal(void);
unsigned short ReadByte();
unsigned short CheckResponse();

Чтобы заставить LCD дисплей работать с нашим микроконтроллером, нам необходимо создать несколько функций задержек. В начале вашего файла XLCD.H говорится, что:

Пользователь должен обеспечить три функции задержки:

  • DelayFor18TCY() обеспечивает задержку в 18 циклов;
  • DelayPORXLCD() обеспечивает задержку в 15 мс;
  • DelayXLCD() обеспечивает задержку в 5 мс.

Мы должны добавить еще и четвертую функцию задержки, Delay10KTCYx

Ниже приведены функция инициализации LCD и функции задержек:

// Функции
void init_XLCD(void){
    OpenXLCD(FOUR_BIT & LINES_5X7);     // Установить 4-битный режим и символы 5x7
    while(BusyXLCD());                  // Проверить, занят ли LCD
    WriteCmdXLCD(0x06);                 // Перенести курсов вправо
    WriteCmdXLCD(0x0C);                 // Дисплей включить, курсор выключить
}

void DelayFor18TCY(void){               // как сказано в файле XLCD.H
    NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); 
    NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); NOP(); 
    return;
}

void DelayPORXLCD(void){               // как сказано в файле XLCD.H
    __delay_ms(15);
}

void DelayXLCD(void){                 // как сказано в файле XLCD.H
    __delay_ms(5);
}

void Delay10KTCYx(unsigned char){     // как сказано в файле XLCD.H
    __delay_ms(10);
}

Далее на очереди функции подачи стартового сигнала, чтения байта и проверки ответа:

void StartSignal(){
    data_dir = 0;       // Установить TRISA2 на выход
    data = 0;           // Установить на RA2 низкий уровень
    __delay_ms(18);     // Ждать 18 мс
    data = 1;           // Установить на RA2 высокий уровень
    __delay_us(20);     // Ждать 20 мс
    data_dir = 1;       // Установить TRISA2 на вход
}

unsigned short ReadByte(){
    unsigned short num = 0, t;
    data_dir = 1;                       // Установить TRISA2 на вход
    for (i=0;i<8;i++){                  // Начать цикл
        while(!data);                   // Когда данные не действительны
        TMR2 = 0;                       // Установить TMR2 в 0
        T2CONbits.TMR2ON = 1;           // Запустить TMR2 от 0, когда обнаруживается фронт
        while(data);                    // импульса, и ждать спада импульса
        T2CONbits.TMR2ON = 0;           // Остановить TMR2, после спада импульса данных
        if(TMR2>40) num |= 1 << (7-i);  // Если время > 40 мкс, то данные равны 1
    }
    return num;                         // Вернуть 8-бит = 1-байт
}

unsigned short CheckResponse(){
    TOUT = 0;
    TMR2 = 0;
    T2CONbits.TMR2ON = 1;       // Включить TMR2
    while(!data && !TOUT);      
    if (TOUT) return 0;         // Вернуть 0 => OK
    else {
        TMR2 = 0;               // Выключить Timer 2
        while(data && !TOUT);   
        if(TOUT) return 0;      // Если Tout = 1, вернуть 0 => OK
        else {
            T2CONbits.TMR2ON = 0;   // Выключить TMR2
            return 1;               // Вернуть 1 => NOT OK   
        }
    }
}

Чтобы узнать, когда микроконтроллер передает сигнал старта, и когда DHT11 закончил передачу своих 40 бит, нам нужна функция прерывания:

void interrupt tc_int(void){
    if(PIR1bits.TMR2IF){            // Если установлен флаг прерывания от совпадения TMR2 и PR2     
        TOUT = 1;
        T2CONbits.TMR2ON = 0;       // Остановить таймер
        PIR1bits.TMR2IF = 0;        // Очистить флаг прерывания TMR2
    }
}

И наконец, основная часть программы:

int main(int argc, char** argv) {
    unsigned short check;
    TRISB = 0b00000000;         // TRISB выход
    PORTB = 0b00000000;         // PORTB низкий уровень
    TRISA = 0b00000001;         // TRISA выход
    PORTA = 0b00000000;         // PORTA низкий уровень
    
    CMCON = 0x07;               // Компараторы выключены
    
    // TIMER
    INTCONbits.GIE = 1;         // Включить глобальное прерывание
    INTCONbits.PEIE = 1;        // Включить прерывание периферии
    PIE1bits.TMR2IE = 1;        // Включить прерывание Timer2
    T2CON = 0;                  // Предделитель 1:1, Timer2 изначально выключен
    PIR1bits.TMR2IF = 0;        // Очистить флаг прерывания TMR INT
    TMR2 = 0;
    
    init_XLCD();                    // Инициализация LCD
    putrsXLCD("  Hello World.");    // Текст приветствия
    SetDDRamAddr(0x40);             // Переместить курсор на строку 2
    putrsXLCD("  I'm alive.");
    __delay_ms(250);
    do {
        __delay_ms(1000);
        StartSignal();              // Передать сигнал старта
        check = CheckResponse();    // Присвоить check 0 = OK, или 1 = NOT OK
        if(!check) {                // OK check = 1 => NOT OK
            WriteCmdXLCD(0x01);     // Очистить экран, установить курсор в позицию 0,0    
            putrsXLCD("No response.");  // Вывести сообщение об ошибке
            SetDDRamAddr(0x40);         
            putrsXLCD("Please check.");
            }
        else {                      // IF check = 0 => OK
    
        RH_Byte1 = ReadByte();      // Прочитать первый байт
        RH_Byte2 = ReadByte();      // Прочитать второй байт
        T_Byte1 = ReadByte();       // Прочитать третий байт
        T_Byte2 = ReadByte();       // Прочитать четвертый байт
        CheckSum = ReadByte();      // Прочитать контрольную сумму
        // Проверить, соответствуют ли все байты контрольной сумме
        if (CheckSum == ((RH_Byte1 + RH_Byte2 + T_Byte1 + T_Byte2) & 0xFF))
        {
            message1[7] = T_Byte1/10 + 48;      // Извлечь десятки
            message1[8] = T_Byte1 + 48;         // Извлечь единицы
            message1[10]= T_Byte2/10 + 48;      // Извлечь десятые доли
            message1[11] = 223;                 // ASCII код для символа градусов          
            message2[7] = RH_Byte1/10 + 48;     // Извлечь десятки
            message2[8] = RH_Byte1 + 48;        // Извлечь единицы
            message2[10] = RH_Byte2/10 + 48;    // Извлечь десятые доли

            WriteCmdXLCD(0x01);
            putrsXLCD(message1);    // Записать значение температуры на LCD
            SetDDRamAddr(0x40);
            putrsXLCD(message2);    // Записать значение влажности на LCD
        }
        else {                      // Контрольная сумма некорректна
            WriteCmdXLCD(0x01);
            putrsXLCD("Checksum error!");
            SetDDRamAddr(0x40);
            putrsXLCD("Please wait.");
        }       
        }
    } while (1);    // Выполнять это постоянно.
}

Это был один из способов использования DHT11 совместно с PIC контроллером и LCD дисплеем.

Заключение

Мы использовали микроконтроллер PIC16F628A совместно с датчиком DHT11 и отобразили значения температуры и относительной влажности воздуха на LCD дисплее. Вы можете, приложив немного усилий, преобразовать/подстроить код для какого-либо другого микроконтроллера. Программа занимает в микроконтроллере 32% памяти данных и 55% памяти программ. Это потому, что микросхема довольно низкого класса.

Разница между PIC16F628 и PIC16F628A в том, что версия с буквой «A» обладает внутренним тактовым генератором. Версия без буквы «A» нуждается во внешнем кварцевом резонаторе или RC цепи. Если вы покупаете PIC16F628, то убедитесь, что он с буквой «A». Другой контроллер является устаревшим.

Фото и видео

Макет термометра на PIC16F628A и DHT11 с LCD
Макет термометра на PIC16F628A и DHT11 с LCD
Макет термометра на PIC16F628A и DHT11 с LCD (микроконтроллер)
Макет термометра на PIC16F628A и DHT11 с LCD (микроконтроллер)
Макет термометра на PIC16F628A и DHT11 с LCD (дисплей)
Макет термометра на PIC16F628A и DHT11 с LCD (дисплей)
Макет термометра на PIC16F628A и DHT11 с LCD (датчик температуры и относительной влажности воздуха)
Макет термометра на PIC16F628A и DHT11 с LCD (датчик температуры и относительной влажности воздуха)

Теги

DHT11LCD дисплейMCUPIC контроллерДатчик температурыИзмерениеИзмерение температурыМикроконтроллерОтносительная влажность воздухаТемпература