Терморегулятор DS18B20 с таймером (Arduino)

Используя цифровой датчик температуры DS18B20, можно собрать простой регулятор температуры. DS18B20 имеет диапазон измерения температуры от -55 до + 125 °C, показания датчика температуры выводятся на дисплей LCD1602 + I2C (I2C модуль на базе микросхем PCF8574 позволяют подключить символьный дисплей 1602 к плате Arduino всего по двум проводам SDA и SCL (А4 и А5), что дает возможность не использовать цифровые выходы Arduino при подключении дисплея.) Органы управления состоят из энкодера KY-040 и двух кнопок.

Терморегулятор может содержать до 10 программ, в каждой программе можно задать температуру регулирования и время (в минутах) поддержания заданной температуры. Так же можно установить гистерезис. Все данные программ и значение гистерезиса сохраняются в энергонезависимой памяти.

Терморегулятор содержит основное меню, меню активации программ и настройки гистерезиса, а так же меню установки параметров (температуры и времени) программ.

Основное меню

На основном меню выводятся:

  1. данные от текущей температуре (от 0 до 125 °С)
  2. индикатор работы нагревательного элемента
  3. выводится номер исполняемой программы и температура регулирования (смена индикации параметра каждые 3 секунды)
  4. оставшееся время работы программы (в минутах)

При нажатии на кнопку энкодера (в основном меню) происходит переход в меню активации программ и установки гистерезиса.

В меню активации программ, при установки параметра «ON» происходит запуск таймера, который отсчитывает время от установленного в программе.

Запуск таймера

 Остановка таймера

Настройка гистерезиса

При запуске таймера происходит отсчет времени исполнения программы P_0 и поддержание заданной в этой программе температуры регулирования, как только время программы P_0 закончится, активируется программа P_1 со своим значение времени и температуры регулирования.

Терморегулятор будет поддерживать температуру по времени заданной в каждой программе до завершения времени работы программы P_9, после чего выход D13 (выход управления нагревательным элементом) становится не активным, происходит переход на программу P_0, которая будет неактивна (отсчет таймера приостанавливается).

Меню установки температуры и времени

На настройки температуры и времени исполнения программ используются кнопки «PROG» и «TEMP / TIMES». При нажатии на кнопку «PROG» происходит переход в меню настройки программ, кнопка энкодера позволяет пролистывать программы P_0…P_9. Кнопка «TEMP / TIMES» позволяет переключать настройки времени и температуры. Параметр меняется при помощи ручки энкодера, температуру можно установить от 0 до 125 °С, время от 0 до 255 минут.

Если время исполнения программы установить на 0 минут (OFF), то эта программа будет неактивна. Например если Вам нужно только 2 программы, то нужно установить в программах P_0 и P_1 время и температуру, а в остальных программа P_2…P_9 время установить на 0 минут (OFF).

Для выхода из режима программирования нужно нажать кнопку «PROG» или подождать 10 секунд для выхода в основное меню.

#include <LiquidCrystal_I2C.h>  // http://forum.rcl-radio.ru/misc.php?action=pan_download&item=45&download=1
#include <OneWire.h>            // http://rcl-radio.ru/wp-content/uploads/2018/07/OneWire.zip
#include <Wire.h>
#include <Encoder.h>            // http://rcl-radio.ru/wp-content/uploads/2019/05/Encoder.zip       
#include <EEPROM.h>
#include <MsTimer2.h>           // http://rcl-radio.ru/wp-content/uploads/2018/11/MsTimer2.zip 
 LiquidCrystal_I2C lcd(0x27,16,2);
 Encoder myEnc(8, 9);// DT, CLK
 OneWire  ds(2); // Вход датчика 18b20
 byte v1[8] = {0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07};
 byte v2[8] = {0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x00};      
 byte v3[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x1F};
 byte v4[8] = {0x1F,0x1F,0x00,0x00,0x00,0x00,0x1F,0x1F};
 byte v5[8] = {0x1C,0x1C,0x00,0x00,0x00,0x00,0x1C,0x1C};
 byte v6[8] = {0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C};
 byte v7[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x07};
 byte v8[8] = {0x1F,0x1F,0x00,0x00,0x00,0x00,0x00,0x00};
 byte d1,d2,d3,d4,d5,d6,e1,e2,e3;
 int a[6],x,t_ds,i_reg,temp_time;
 int reg_t[10],reg_time[10],reg_t0,reg_time0;
 bool prog,w;
 unsigned long times,times1,times_baza,times_cur,oldPosition  = -999,newPosition;
 int tic,tac,power,menu,gis=1,nagrev;
 
void setup(){
  Wire.begin();lcd.init();lcd.backlight();
  MsTimer2::set(1, to_Timer);MsTimer2::start();
  if(EEPROM.read(100)!=0){for(int i=0;i<101;i++){EEPROM.update(i,0);}}// очистка памяти при первом включении
  lcd.createChar(1, v1);lcd.createChar(2, v2);lcd.createChar(3, v3);lcd.createChar(4, v4);
  lcd.createChar(5, v5);lcd.createChar(6, v6);lcd.createChar(7, v7);lcd.createChar(8, v8);
  Serial.begin(9600);  
  pinMode(10,INPUT);       // button SW ENCODER 
  pinMode(3,INPUT_PULLUP); // button PROG
  pinMode(4,INPUT_PULLUP); // button PROG_TEMP_TIME
  pinMode(13,OUTPUT);      // OUTPUT REG TEMP
  for(int i=0;i<10;i++){reg_t[i] = EEPROM.read(i);}
  for(int i=10;i<20;i++){reg_time[i-10] = EEPROM.read(i);}if(reg_time[0]==0){reg_time[0]=1;}
  gis = EEPROM.read(20);
  digitalWrite(13,LOW);
  } 
 
void loop(){
////// BUTTON /////////////////////////////////////////////////////////  
  if(digitalRead(3)==LOW&&prog==0){prog=1;menu=0;temp_time=0;times1=millis();w=1;lcd.clear();delay(300);}
  if(digitalRead(3)==LOW&&prog==1){prog=0;temp_time=0;times1=millis();w=1;lcd.clear();delay(300);}
  if(digitalRead(4)==LOW&&prog==1){temp_time++;if(temp_time>1){temp_time=0;}times1=millis();w=1;delay(300);lcd.clear();}
  if(digitalRead(10)==LOW&&prog==1){i_reg++;temp_time=0;if(i_reg>9){i_reg=0;}times1=millis();w=1;lcd.clear();delay(300);}
  if(digitalRead(10)==LOW&&prog==0){menu++;times1=millis();w=1;lcd.clear();delay(300);if(menu>2){menu=0;}}
 
///////// REG_TEMP ///////////////////////////////
  if(prog==1&&temp_time==0){
   switch(i_reg){
     case 0: reg_t0 = reg_t[0];break;
     case 1: reg_t0 = reg_t[1];break;
     case 2: reg_t0 = reg_t[2];break;
     case 3: reg_t0 = reg_t[3];break;
     case 4: reg_t0 = reg_t[4];break;
     case 5: reg_t0 = reg_t[5];break;
     case 6: reg_t0 = reg_t[6];break;
     case 7: reg_t0 = reg_t[7];break;
     case 8: reg_t0 = reg_t[8];break;
     case 9: reg_t0 = reg_t[9];break;}
 
 if(newPosition != oldPosition){oldPosition = newPosition;reg_t0=reg_t0+newPosition;myEnc.write(0);newPosition=0;times1=millis();;w=1;temp_func();}
 
   switch(i_reg){
     case 0: reg_t[0] = reg_t0;break;
     case 1: reg_t[1] = reg_t0;break;
     case 2: reg_t[2] = reg_t0;break;
     case 3: reg_t[3] = reg_t0;break;
     case 4: reg_t[4] = reg_t0;break;
     case 5: reg_t[5] = reg_t0;break;
     case 6: reg_t[6] = reg_t0;break;
     case 7: reg_t[7] = reg_t0;break;
     case 8: reg_t[8] = reg_t0;break;
     case 9: reg_t[9] = reg_t0;break;}  
  }  
/////// REG_TIME //////////////////////////////////////////////
  if(prog==1&&temp_time==1){
   switch(i_reg){
     case 0: reg_time0 = reg_time[0];break;
     case 1: reg_time0 = reg_time[1];break;
     case 2: reg_time0 = reg_time[2];break;
     case 3: reg_time0 = reg_time[3];break;
     case 4: reg_time0 = reg_time[4];break;
     case 5: reg_time0 = reg_time[5];break;
     case 6: reg_time0 = reg_time[6];break;
     case 7: reg_time0 = reg_time[7];break;
     case 8: reg_time0 = reg_time[8];break;
     case 9: reg_time0 = reg_time[9];break;}
 
 if(newPosition != oldPosition){oldPosition = newPosition;reg_time0=reg_time0+newPosition;myEnc.write(0);newPosition=0;times1=millis();;w=1;time_func();}
 
   switch(i_reg){
     case 0: reg_time[0] = reg_time0;break;
     case 1: reg_time[1] = reg_time0;break;
     case 2: reg_time[2] = reg_time0;break;
     case 3: reg_time[3] = reg_time0;break;
     case 4: reg_time[4] = reg_time0;break;
     case 5: reg_time[5] = reg_time0;break;
     case 6: reg_time[6] = reg_time0;break;
     case 7: reg_time[7] = reg_time0;break;
     case 8: reg_time[8] = reg_time0;break;
     case 9: reg_time[9] = reg_time0;break;}  
  }  
 
  if(prog==1&&menu==0){
    lcd.setCursor(0,0);if(temp_time==0){lcd.print("*REG_TEMP");}else{lcd.print(" REG_TEMP");}lcd.print(i_reg);
    lcd.print(" ");lcd.print(reg_t[i_reg]);lcd.print(" ");lcd.setCursor(15,0);;lcd.print("C");
    lcd.setCursor(0,1);if(temp_time==1){lcd.print("*REG_TIME");}else{lcd.print(" REG_TIME");}lcd.print(i_reg);
    if(reg_time[i_reg]>0){lcd.print(" ");lcd.print(reg_time[i_reg]);lcd.print("  ");lcd.setCursor(15,1);lcd.print("M");}
    else{lcd.print(" ");lcd.print("OFF     ");}
    }
 ///////// READ TEMP /////////////////////////////////////////////
 if(millis()-times>1000){times=millis();t_ds = dsRead(0)*10;}// Измерять температуру 1 раз в секунду
 ///////// LCD BIG ///////////////////////////////////////////////
 if(prog==0&&menu==0){
     a[0]=t_ds/1000;
     a[1]=t_ds/100%10;
     a[2]=t_ds/10%10;
     a[3]=t_ds%10;
     if(t_ds<1000){a[0]=10;}
     if(t_ds<100){a[1]=10;}
   for(x=0;x<4;x++){
    switch(x){
        case 0: e1=0;e2=0,e3=1;break;
        case 1: e1=2,e2=3,e3=4;break;
        case 2: e1=5,e2=6,e3=7;break;
        case 3: e1=9,e2=10,e3=11;break;
   }digit();}
   lcd.setCursor(8,1);lcd.print(".");
   if(power==1){lcd.setCursor(13,1);lcd.print((reg_time[tac]-tic)/100);lcd.print((reg_time[tac]-tic)/10%10);lcd.print((reg_time[tac]-tic)%10);}
   else{lcd.setCursor(13,1);lcd.print("OFF");}
   if(millis()-times_cur<=3000){lcd.setCursor(13,0);lcd.print("P_");lcd.print(tac);}
   else if(millis()-times_cur>3000&&millis()-times_cur<6000){lcd.setCursor(13,0);lcd.print(reg_t[tac]/100);lcd.print(reg_t[tac]/10%10);lcd.print(reg_t[tac]%10);}
   else{times_cur=millis();}
 
//////////////////////////////////////////////////////////////////
}
////////// MENU /////////////////
if(prog==0&&menu==1){
  if(newPosition != oldPosition){oldPosition = newPosition;power=power+newPosition;myEnc.write(0);newPosition=0;times1=millis();w=1;if(power>1){power=0;}if(power<0){power=1;}}
  lcd.setCursor(0,0);lcd.print("heating");
  if(power==1){lcd.print(" ON ");}
  if(power==0){lcd.print(" OFF");tac=0;tic=0;}
  }
if(prog==0&&menu==2){
  if(newPosition != oldPosition){oldPosition = newPosition;gis=gis+newPosition;myEnc.write(0);newPosition=0;times1=millis();w=1;gis_fun();}
  lcd.setCursor(0,0);lcd.print("hysteresis ");lcd.print((float)gis/10,1);
  }  
 
///////// EEPROM ////////////////////////////////////////////////
  if(millis()-times1>10000 && w==1){
   for(int i=0;i<10;i++){EEPROM.update(i,reg_t[i]);}
   for(int i=10;i<20;i++){EEPROM.update(i,reg_time[i-10]);}
   EEPROM.update(20,gis);
   menu=0;prog=0;i_reg=0;w=0;temp_time=0;lcd.clear();} 
 
//////// AVTO REG TIMER /////////////////////////////////////////////
 
  if(millis()-times_baza>60000 && power==1){tic++;times_baza=millis();}
  if(reg_time[tac]==0){tac++;}
  if(tic>reg_time[tac]-1){tic=0;tac++; if(tac>9){tac=0;tic=0;power=0;}}
  if(power==0){times_baza=millis();digitalWrite(13,LOW);}
  if(power==1){
  if(reg_t[tac]*10 >= t_ds + gis){digitalWrite(13,HIGH);nagrev=1;}
  if(reg_t[tac]*10 <= t_ds - gis){digitalWrite(13,LOW);nagrev=0;}  
  }
  if(nagrev==1&&menu==0&&prog==0){lcd.setCursor(12,0);lcd.print("*");}
  if(nagrev==0&&menu==0&&prog==0){lcd.setCursor(12,0);lcd.print(" ");}
 
  // Serial.print(reg_t[tac]*10);Serial.print("  ");Serial.print(t_ds);Serial.print("  ");Serial.println(gis);
 
  }// LOOP END 
 
void gis_fun(){if(gis<0){gis=0;}if(gis>30){gis=30;}}  
void time_func(){if(reg_time0>255){reg_time0=255;}if(reg_time0<0){reg_time0=0;}}
void temp_func(){if(reg_t0>125){reg_t0=125;}if(reg_t0<0){reg_t0=0;}}
void to_Timer(){newPosition = myEnc.read()/4;}  
 
void digit(){
  switch(a[x]){
    case 0: d1=1,d2=8,d3=6,d4=1,d5=3,d6=6;break;
    case 1: d1=32,d2=2,d3=6,d4=32,d5=32,d6=6;break;
    case 2: d1=2,d2=8,d3=6,d4=1,d5=4,d6=5;break;
    case 3: d1=2,d2=4,d3=6,d4=7,d5=3,d6=6;break;
    case 4: d1=1,d2=3,d3=6,d4=32,d5=32,d6=6;break;
    case 5: d1=1,d2=4,d3=5,d4=7,d5=3,d6=6;break;
    case 6: d1=1,d2=4,d3=5,d4=1,d5=3,d6=6;break;
    case 7: d1=1,d2=8,d3=6,d4=32,d5=32,d6=6;break;
    case 8: d1=1,d2=4,d3=6,d4=1,d5=3,d6=6;break;
    case 9: d1=1,d2=4,d3=6,d4=7,d5=3,d6=6;break;
    case 10:d1=150,d2=150,d3=150,d4=150,d5=150,d6=150;break;
    }
if(x>0){lcd.setCursor(e1,0);lcd.write((uint8_t)d1);}
lcd.setCursor(e2,0);lcd.write((uint8_t)d2);lcd.setCursor(e3,0);lcd.write((uint8_t)d3);lcd.setCursor(e1,1);
lcd.write((uint8_t)d4);lcd.setCursor(e2,1);lcd.write((uint8_t)d5);lcd.setCursor(e3,1);lcd.write((uint8_t)d6);}   
 
float dsRead(byte x) {
  byte data[2], addr[8][8], kol = 0;
  while (ds.search(addr[kol])) {  // поиск датчиков, определение адреса и кол-ва датчиков
    kol++;
  } 
  ds.reset_search();  // Сброс поиска датчика
  ds.reset();         // Инициализация, выполняется сброс шины
  ds.select(addr[x]); // Обращение к датчику по адресу
  ds.write(0x44, 0);  // Измерение температуры с переносом данных в память
  ds.reset();         // Инициализация, выполняется сброс шины
  ds.select(addr[x]); // Обращение к датчику по адресу
  ds.write(0xBE);     // Обращение памяти
  data[0] = ds.read();// Чтение памяти byte low
  data[1] = ds.read();// Чтение памяти byte high
  float value = ((data[1] << 8) | data[0]) / 16.0; return (float)value; // Расчет температуры и вывод
}

Comments

  1. Приветствую.
    Есть ли возможность коррекции показаний датчика?
    у ds18b20 после 85С начинается сильная погрешность, у меня на 100С показывает 96.5 ((

  2. С коррекцией температуры вроде разобрались, сейчас провел тестовый нагрев вроде все хоро, но вылезла другая проблема. Таймер обратного отсчета запускается не по достижении заданной температуры, а при запуске нагрева ((((((((((((((

      1. Благодарю Вас. Мне нужно для автоклава, там как раз нужно запускать таймер после выхода на установленное значение температуры. Не буду переносить с макетки пока проект, буду ждать от Вас новостей. P.S. Если будите что то дописывать, вы наглые «хотелки» принимаете или нет? P.S.S. Может тогда описание чуток скорректировать, по нему как раз выходит что таймер поддерживает заданную температуру как в вашем проекте с MAX6675. Еще раз благодарю вас, жду новостей.

        1. Здравствуйте. Скажите пожалуйста, Вы решили проблему с автоклавом? Тоже ищу термотаймер с отсчетом времени после выхода на нужную температуру.

Добавить комментарий

Войти с помощью: