Достаточно часто для конструирования термометров на основе микроконтроллеров используется датчик LM35 поскольку он дешев и прост в использовании. Но датчик LM35 аналоговый, поэтому для считывания с него значения температуры необходимо задействовать АЦП (аналого-цифровой преобразователь) микроконтроллера. Поэтому в данном проекте для измерения температуры мы решили использовать датчик DS18B20, при подключении которого к микроконтроллеру PIC нет необходимости в использовании АЦП.
Измеряемое значение температуры мы будем выводить на экран ЖК дисплея 16х2. Общие принципы работы нашего цифрового термометра на основе микроконтроллера PIC16F877A и датчика DS18B20 будут следующие:
- Мы будем показывать на экране ЖК дисплея весь измеряемый датчиком диапазон температур: от -55 до +125 градусов.
- Изменять выводимое на экран ЖК дисплея значение температуры только если произошло ее изменение не менее чем на + / - 0.2 градуса.
Ранее на нашем сайте мы рассматривали подключение датчика DS18B20 к плате Arduino, плате Raspberry Pi и микроконтроллеру AVR.
Необходимые компоненты
- Микроконтроллер PIC16F877A (купить на AliExpress).
- Датчик температуры DS18B20 (купить на AliExpress).
- Держатель микросхем на 40 контактов (купить на AliExpress).
- Программатор PICkit 3 (купить на AliExpress).
- Кварцевый генератор 20 МГц (купить на AliExpress).
- Конденсаторы 33 пФ (2 шт.) (купить на AliExpress).
- Резистор 4,7 кОм – 2 шт. (купить на AliExpress).
- Потенциометр 10 кОм (купить на AliExpress).
- ЖК дисплей 16х2 (купить на AliExpress).
- Светодиод (купить на AliExpress).
- Адаптер 5V.
- Макетная плата и соединительные провода.
Датчик температуры DS18B20
Датчик DS18B20 широко применяется в современной электронике и обеспечивает отличную точность измерения температуры. Разрешающая способность датчика составляет от 9 до 12 бит. На выход значение температуры датчик передает через интерфейс one wire и поэтому для считывания с него значений АЦП не требуется.
Технические характеристики датчика DS18B20:
- диапазон измеряемых температур: от -55°C до +125°C (от -67°F до +257°F);
- точность измерения температуры в диапазоне от -10°C до +85°C: ±0.5°C;
- программно настраиваемое разрешение от -10°C до +85°C;
- не требует никаких внешних компонентов для своей работы;
- интерфейс: 1-Wire®.
Внешний вид и распиновка датчика DS18B20 показаны на следующем рисунке.
Как видно из рисунка, по внешнему виду датчик DS18B20 очень похож на транзисторы BC547 или BC557. Датчик имеет 3 контакта: Ground, DQ (выходной контакт) и VCC.
На следующем рисунке показаны технические характеристики из даташита на датчик DS18B20, которые потребуются нам в нашем проекте. Как видно из представленных характеристик, напряжения питания для датчика составляет от +3.0V до +5.5V. Также необходим подтягивающий резистор для питающего напряжения.
Для диапазона от -10°C до +85°C точность измерения температуры датчиком составляет ±0.5°C, а для полного диапазона от -55°C до +125°C она составляет ±2°C.
Как следует из даташита на датчик DS18B20 мы можем подключить его либо в двухпроводном режиме, в котором для его подключения нам будут необходимы всего два провода – DATA или GND, либо в режиме с внешним питанием, в котором для его подключения необходимы три провода. Мы в нашем проекте будем использовать трехпроводное подключение датчика.
Схема проекта
Схема подключения датчика температуры DS18B20 к микроконтроллеру PIC представлена на следующем рисунке.
В представленной схеме мы подключили контакты RS, R/W и E ЖК дисплея к контактам RB0, RB1, RB2 микроконтроллера PIC16F877A, а контакты D4, D5, D6, D7 ЖК дисплея – к контактам RB4, RB5, RB6 и RB7 микроконтроллера. ЖК дисплей будет работать в 4 битном режиме.
Кварцевый генератор 20MHz с помощью двух керамических конденсаторов подключен к контактам OSC1 и OSC2 микроконтроллера PIC. Он обеспечивает стабильную тактовую частоту 20MHz для микроконтроллера.
Датчик DS18B20 подключен к контакту микроконтроллера с помощью подтягивающего резистора 4,7 кОм.
Алгоритм работы программы термометра
В программе для нашего цифрового термометра на основе микроконтроллера PIC и датчика DS18B20 нам необходимо выполнить следующую последовательность шагов:
- Настроить биты конфигурации микроконтроллера, включая биты, отвечающие за работу с кварцевым генератором.
- Настроить порт микроконтроллера, к которому подключен ЖК дисплей.
- Каждый цикл датчик DS18B20 будет начинать со сброса, поэтому нам необходимо сбрасывать DS18B20 и ждать наличия импульса.
- Установить разрешение работы датчика равное 12 бит.
- Пропустить считывание из ROM, следующее за импульсом сброса.
- Подтвердить команду преобразования температуры.
- Считать значение температуры из scratchpad (временной памяти).
- Проверить значение температуры является положительным или отрицательным.
- Вывести значение температуры на экран ЖК дисплея.
- Подождать пока значение температуры изменится на величину не менее чем +/-.20 градуса Цельсия.
Объяснение программы для микроконтроллера PIC
Полный код программы приведен в конце статьи, здесь же мы кратко рассмотрим его основные фрагменты.
Первым делом в программе настроим биты конфигурации микроконтроллера. Затем подключим необходимые заголовочные файлы: lcd.h, ds18b20.h и xc.h.
1 2 3 4 |
#include <xc.h> #include <string.h> #include "supporting c files/ds18b20.h" #include "supporting c files/lcd.h" |
Затем укажем команды, которые мы будем передавать на датчик температуры. Эти команды перечислены в даташите на датчик.
1 2 3 4 5 |
#define skip_rom 0xCC #define convert_temp 0x44 #define write_scratchpad 0x4E #define resolution_12bit 0x7F #define read_scratchpad 0xBE |
На приведенном рисунке в таблице 3 из даташита на датчик показаны команды, которые можно подавать на датчик.
Мы будем изменять значения температуры на экране ЖК дисплея только если температура изменится не менее чем на +/- .20 градуса. Мы можем изменить этот температурный диапазон, изменив макрос temp_gap.
Остальные две переменные вещественного типа используются для хранения отображаемого значения температуры и его сравнения с диапазоном, определенным temp_gap.
1 2 |
#define temp_gap 20 float pre_val=0, aft_val=0; |
Далее в функции void main() функция lcd_init(), которая вызывается из библиотеки lcd.h, используется для инициализации ЖК дисплея.
Регистр TRIS используется для задания режима работы контактов микроконтроллера – на ввод или вывод данных. Переменные TempL и TempH используются для хранения 12-битного разрешения датчика температуры.
1 2 3 4 5 6 7 8 9 |
void main(void) { TRISD = 0xFF; TRISA = 0x00; TRISB = 0x00; //TRISDbits_t.TRISD6 = 1; unsigned short TempL, TempH; unsigned int t, t2; float difference1=0, difference2=0; lcd_init(); |
Следующий фрагмент кода используется для проверки того подключен ли датчик температуры или нет.
1 2 3 4 5 6 |
while(ow_reset()){ lcd_com(0x80); lcd_puts ("Please Connect "); lcd_com (0xC0); lcd_puts("Temp-Sense Probe"); } |
Далее мы инициализируем датчик температуры и передаем ему команду на преобразование значения температуры.
1 2 3 4 5 6 7 8 9 |
lcd_puts (" "); ow_reset(); write_byte(write_scratchpad); write_byte(0); write_byte(0); write_byte(resolution_12bit); // 12bit resolution ow_reset(); write_byte(skip_rom); write_byte(convert_temp); |
Затем мы сохраняем 12-битное значение температуры в двух переменных типа unsigned short.
1 2 3 4 5 6 7 |
while (read_byte()==0xff); __delay_ms(500); ow_reset(); write_byte(skip_rom); write_byte(read_scratchpad); TempL = read_byte(); TempH = read_byte(); |
Далее в программе мы с помощью условных операторов if проверяем является ли значение температуры положительным или отрицательным и изменилось ли ее значение на величину +/- .20 градуса или нет.
Считывание данных с датчика температуры DS18B20
Давайте посмотрим на интервалы времени в интерфейсе 1-Wire®. Мы используем кварцевый генератор на 20Mhz. Если мы посмотрим внутрь файла ds18b20.c, мы там увидим:
1 |
#define _XTAL_FREQ 20000000 |
Эта команда задает временную задержку для компилятора XC8. В качестве тактовой частоты у нас используется частота 20Mhz.
Мы запрограммируем 5 функций:
- ow_reset.
- read_bit.
- read_byte.
- write_bit.
- write_byte.
Протокол 1-Wire® требует соблюдения строгих временных рамок (интервалов). Их значения можно найти в даташите на данный протокол.
Внутри функции ow_reset(void) мы создадим точное значение временного слота – это очень важно для работы с протоколом 1-Wire®.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
unsigned char ow_reset(void) { DQ_TRIS = 0; // Tris = 0 (output) DQ = 0; // set pin# to low (0) __delay_us(480); // 1 wire require time delay DQ_TRIS = 1; // Tris = 1 (input) __delay_us(60); // 1 wire require time delay if (DQ == 0) // if there is a presence pluse { __delay_us(480); return 0; // return 0 ( 1-wire is presence) } else { __delay_us(480); return 1; // return 1 ( 1-wire is NOT presence) } } // 0=presence, 1 = no part |
Затем, используя это точное значение временного слота, мы запрограммируем функции для чтения и записи данных в датчик.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
unsigned char read_bit(void) { unsigned char i; DQ_TRIS = 1; DQ = 0; // pull DQ low to start timeslot DQ_TRIS = 1; DQ = 1; // then return high for (i=0; i<3; i++); // delay 15us from start of timeslot return(DQ); // return value of DQ line } void write_bit(char bitval) { DQ_TRIS = 0; DQ = 0; // pull DQ low to start timeslot if(bitval==1) DQ =1; // return DQ high if write 1 __delay_us(5); // hold value for remainder of timeslot DQ_TRIS = 1; DQ = 1; }// Delay provides 16us per loop, plus 24us. Therefore delay(5) = 104us |
Все необходимые библиотеки и .c файлы для этого проекта вы можете скачать по следующей ссылке.
Исходный код программы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
/* * File: main.c * Author: Sourav Gupta * * Created on 11 April 2018, 17:57 */ /* * Configuration Related settings. Specific for microcontroller unit. */ #pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) #pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) #pragma config PWRTE = OFF // Power-up Timer Enable bit (PWRT disabled) #pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) #pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3/PGM pin has PGM function; low-voltage programming enabled) #pragma config CPD = OFF // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off) #pragma config WRT = OFF // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control) #pragma config CP = OFF // Flash Program Memory Code Protection bit (Code protection off) #define _XTAL_FREQ 20000000 /* * System Header files inclusions */ #include <xc.h> #include <string.h> #include "supporting c files/ds18b20.h" #include "supporting c files/lcd.h" /* * Ds18b20 related definition */ #define skip_rom 0xCC #define convert_temp 0x44 #define write_scratchpad 0x4E #define resolution_12bit 0x7F #define read_scratchpad 0xBE /* * User interface related definitions */ #define temp_gap 20 float pre_val=0, aft_val=0; /* * Program flow related functions */ void sw_delayms(unsigned int d); /* Main function / single Thread*/ void main(void) { TRISD = 0xFF; TRISA = 0x00; TRISB = 0x00; //TRISDbits_t.TRISD6 = 1; unsigned short TempL, TempH; unsigned int t, t2; float difference1=0, difference2=0; lcd_init(); while(1){ float i=0; /* This is for presence detection of temp-sensing probe*/ while(ow_reset()){ lcd_com(0x80); lcd_puts ("Please Connect "); lcd_com (0xC0); lcd_puts("Temp-Sense Probe"); } /*------------------------------------------------------*/ lcd_puts (" "); ow_reset(); write_byte(write_scratchpad); write_byte(0); write_byte(0); write_byte(resolution_12bit); // 12-битное разрешение ow_reset(); write_byte(skip_rom); write_byte(convert_temp); while (read_byte()==0xff); __delay_ms(500); ow_reset(); write_byte(skip_rom); write_byte(read_scratchpad); TempL = read_byte(); TempH = read_byte(); /*This is for Negative temperature*/ /*If result (TempH [Bitwise and] 1000 0000) = not 0 *then this condition get true. case1. -0.5 degree value = 1111 1111. [1111 1111 & 1000 0000 = 1000 0000 which is not 0.] case2. -55 degree value = 1111 1100. [1111 1100 & 1000 0000 = 1000 0000 which is not 0] 0x80 = 1000 0000 Test Case -10.125 output 1111 1111 0101 1110*/ if((TempH & 0x80)!=0){ // If condition will execute as TempH = 1111 1111 & 1000 0000 = 1000 0000. t=TempH;// Store tempH value in t = 1111 1111 . t<<=8;//after bitwise left shift 8 times value in t will be 1111 1111 0000 0000. t=t|TempL;// t = 1111 1111 0000 0000 | 0101 1110 [ result t = 1111 1111 0101 1110] t=t-1;//t = t-1 in this case t = 1111 1111 0101 1101. t=~t;// t = 0000 0000 1010 0010. t>>=4;// t = 0000 0000 0000 1010. t=t*100;// t = 10 * 100 = 1000. t2=TempL; //Store tempL value = 0101 1110. t2=t2-1;// t2= 0101 1101 t2=~t2;//t2 = 1010 0010 t2=t2&0x0f;// t2 = 1010 0010 | 0000 1111 = 0000 0010 t2=t2 * 6.25; // 0000 00010 = 2 x 6.25 = 12.50 i=((unsigned int)t ) + (unsigned int)t2; //put both value in one variable 1000 + 12.5 = 1012.5 /*This if-else condition done because LCD would not refresh till temperature change -.20 or +.20 degree*/ pre_val=aft_val; difference1 = pre_val - i; difference2 = i - pre_val; if(difference1 > temp_gap || difference2 > temp_gap){ aft_val = i; lcd_com (0x80); lcd_puts ("Circuit Digest"); lcd_com (0xc0); lcd_puts("-"); lcd_bcd (5,aft_val); lcd_data(223); lcd_puts("C "); } else{ lcd_com (0x80); lcd_puts ("Circuit Digest"); lcd_com (0xc0); lcd_puts("-"); lcd_bcd (5,pre_val); lcd_data(223); lcd_puts("C "); } } /*This is for positive Temperature*/ else { i=((unsigned int)TempH << 8 ) + (unsigned int)TempL; //put both value in one variable i = i * 6.25; //calculations used from the table provided in the data sheet of ds18b20 /*This if-else condition done because LCD would not refresh till temperature change -.20 or +.20 degree*/ pre_val=aft_val; difference1 = pre_val - i; difference2 = i - pre_val; if(difference1 > temp_gap || difference2 > temp_gap){ aft_val = i; lcd_com (0x80); lcd_puts ("Circuit Digest"); lcd_com (0xc0); lcd_bcd (5,aft_val); lcd_data(223); lcd_puts("C "); } else{ lcd_com (0x80); lcd_puts ("Circuit Digest"); lcd_com (0xc0); lcd_bcd (5,pre_val); lcd_data(223); lcd_puts("C "); } } } return; } void sw_delayms(unsigned int d){ int x, y; for(x=0;x<d;x++) for(y=0;y<=1275;y++); } |