Как использовать датчик температуры и влажности воздуха 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». Другой контроллер является устаревшим.