Как использовать датчик температуры и влажности воздуха DHT11 с PIC16F628A и LCD
В данной статье мы рассмотрим, как, используя PIC-контроллер, можно считать из DHT11 значения температуры и относительной влажности воздуха и показать их на LCD. В этом примере мы будем использовать микроконтроллер PIC16F628A.
Нам потребуется
Чтобы выполнить этот проект вам понадобится следующее:
- компьютер с установленными MPLAB X IDE и компилятором XC8 версии 1.34 от Microchip (я использую MPLAB X версии 3.05 и XC8 версии 1.34);
- микроконтроллер PIC16F628;
- LCD (HD44780 или аналог);
- датчик температуры и относительной влажности воздуха DHT11;
- инструмент для прошивки микроконтроллера (я испльзую PICkit3);
- компоненты, перечисленные в перечне элементов из Eagle;
- если вы хотите собрать макет схемы, то вам понадобится макетная плата и несколько перемычек.
Введение
DHT11 – это датчик температуры и влажности воздух, который использует один провод для передачи 40 бит данных. Первые 16 бит – это целая и дробная части значения влажности, следующие 16 бит – целая и дробная части значения температуры, и последние 8 бит – это контрольная сумма.
Чтобы DHT11 и микроконтроллер начали общаться друг с другом, их необходимо засинхронизировать. Чтобы засинхронизировать их, микроконтроллер посылает сигнал старта, который представляет собой импульс длительностью 20 мкс на выводе данных. После импульса, микроконтроллер ждет получения данных. В программе мы должны изменить направление вывода данных. Вы можете прочитать подробности о DHT11 в техническом описании. У вас может быть датчик, как в 4-выводном, так и в 3-выводном корпусе; но мы используем 3-выводную версию. Между этими версиями нет никакой разницы, а дополнительный вывод ни к чему не подключен.

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

(может показаться избыточным, составлять структурную схему для каждой мелочи, но это может быть полезно)
Мы хотим, чтобы DHT11 передавал данные микроконтроллеру.
Мы хотим, чтобы микроконтроллер обрабатывал данные и отображал их на LCD дисплее.
Мы хотим, чтобы у нас была возможность прошивать микроконтроллер через ICSP.
Принципиальная схема
Принципиальная схема разделена на блоки.

- Блок питания
- Приводит входное напряжение к значению 5 вольт. Использует стабилизатор напряжения LM7805.
- ICSP
- Это 5-пиновый разъем, подключенный к выводам программирования микроконтроллера. Мы используем этот разъем для прошивки микроконтроллера.
- DHT11
- Это 3-пиновый разъем, к которому подключен датчик. Средний вывод подключен к микроконтроллеру для передачи данных.
- MCU
- Это PIC16F628, который принимает данные от DHT11 и выводит их на LCD дисплей.
- Дисплей
- LCD дисплей (2 строки по 16 символов), который показывает влажность и температуру воздуха.
Микроконтроллер у нас будет работать от внутреннего тактового генератора с частотой 4 МГц. Поэтому на схеме нам не нужен кварцевый резонатор.
Eagle сгенерировала нам следующий перечень элементов:
Позиционное обозначение | Номинал | Элемент | Корпус | Библиотека | Лист |
---|---|---|---|---|---|
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». Другой контроллер является устаревшим.
Фото и видео



