Автоматика на контроллере ARDUINO v.2


Автоматика для любого самогонного аппарата с сухопарником на основе контроллера arduino/ардуино.

Случилось чудо и все основное по моему новому блоку управления самогонным аппаратом закончено. Основное - потому, что это далеко не конечный вариант.

В этот раз решил сразу все подробно изложить, чтобы было все подробно.

Что в настоящий момент реализовано в самогонном аппарате:

  1.  легко редактируемое меню с навигацией джойстиком.
  2.  управление венилятором охлаждения семистора в зависимости от режима нагрева
  3.  возможность ручного и автоматического управления нагревом
  4.  в автоматическом режиме аппарат может самостоятельно управлять нагревом на всем протяжении перегонки и закончить перегонку по достижении заданной температуры в перегонном кубе.
  5.  есть возможность включить звуковое предупреждение / оповещение для каких-либо событий.
  6. Передача показаний температуры на смартфон/планшет через блютуз (тема на форуме) (постараюсь восстановить как статью).

Также, еще точно будет реализовываться возможность перевода спирта сырца в дополнительную ёмкость, после наполнения основной.


Не для коммерческого использования. Все содержание публикации "для свободного использования". Возможны любые изменения в конструкции и программе. Свободное изготовление для личного пользования, но не для третьих лиц без согласования с автором. Автор и администрация сайта снимает с себя ответственность за любые последствия самостоятельного воспроизведения нижеизложенного.

Данное автоматическое управление температурным режимом может быть реализовано на любом самогонном аппарате (дистилляторе) с сухопарником и объемом куба 10 - 25 литров, с ТЭНом мощностью 1- 2,5 КВт (симистор BTA41-800B по даташиту до 40А).

 

     

Новое в  управлении тэном то, что сигнал на включение и отключение симистору BTA41-800B дает не реле, как это было в предыдущей версии (точней схема подключения там присутствует, но реализовано только сейчас), а оптопара со встроенной микросхемой, которая отслеживает прохождение переменного тока через ноль и в этот момент происходит переключение.

управление ТЭНом в самогонном аппарате

В этом есть как преимущество, так и недостаток. Плюс в том, что переключение происходит при отсутствии напряжения и работать все будет плавно и без скачков напряжения. Минус - необходимость ориентироваться при переключениях на время полупериода колебаний в электросети, так как если мы захотим переключать чаще, то эта схема все равно включится только при прохождении тока через ноль. Частота в электросети России по ГОСТу должна составлять 50+-0,2 Гц, будем считать исходя из этого. Частота - это полный цикл колебаний за секунду времени. Секунда - 1000 мс, 1000/50 - время полного периода, следовательно полупериод равен 10 мс, следовательно именно с таким минимальным интервалом можно управлять тэном. Но это минимальный интервал и на мой взгляд незачем так часто дергать ТЭН. Интервалы 200-900 мс очень даже приемлемы. Если выделить отдельно с шагом 100 мс 7 значений на нагрев и столько же на паузы, то получится 49 вариантов нагрева - очень неплохо. На этом пока и остановимся, изменить это можно достаточно просто.

По поводу управления: убираем кнопки-лампочки, оставляем один дисплей и устанавливаем джойстик. Аскетизм и функциональность...))) ...экономия в 2 пина только на кнопках, а функциональность определяется только нашими потребностями.

подключение джойстика

 Подключение экрана-шилда для arduino nokia 5510 (кстати, этот дисплей будет работать также на пинах и без шим display = Adafruit_PCD8544(12, 8, 7, 4, 2), т.е. при дифиците пинов с шим можно переподключить дисплей):

подключение дисплея nokia5510

Подключение вентилятора охлаждения радиатора симистора через n-канальный транзистор, который кстати совершенно бесплатно был выдран из дохлой материнской платы компьютера))):

подключения вентилятора через транзистор

Контроллер запитывается от блока питания на 12В и от него же берется питание для вентилятора, с понижением через автомобильный юсб-адаптер до 5 вольт, для снижения оборотов вентилятора.

Подключение 3х датчиков DALLAS 18B20:

Подключение 3х датчиков DALLAS 18B20 к ARDUINO

Схема подключения устройств по пинам:

Схема подключения устройств к ардуино

Схема подключения составленная Евгением:

Схема подключения устройств к ардуино

После подключения всех устройств, в наличии у нас остается 3 цифровых (можно освободить еще 1 за счет бипера) пина, 2 из которых с ШИМ - это под сервы для переключения между емкостями для голов и тела, и для слива сухопарника. Планирую освободить пин 3, перекинув его на 2ой пин, т.к. кнопка джойстика не используется, и занять этот пин с шим под управление вентилятором для охлаждения царги. Также есть 2 аналоговых пина, один из которых еще на одну кнопку контроля крепости. По идее должно хватить, по крайней мере пока ничего больше не планирую))).

Внешний вид автоматики:

внешний вид автоматики самогонного аппарата

 

Внутренняя компановка:

внутренняя компановка автоматики

Итак меню, с помощью которого будем управлять процессом:

menu coladge new

Здесь все интуитивно понятно, все строки информационные, кроме последней, в которой и происходит выбор конкретных значений для параметров.

Добавляются пункты меню вместе с переменными легко и непринужденно, так что при необходимости сделать это можно за пару минут.

 

Настоящая идея для реализации:

 

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

схема аппарата

Хоть погрешность датчика dallas 18b20 составляет 0.5 градуса, дискретность его измерения 0.0612 (насколько я помню), для программной обработки данных от датчиков этого более чем достаточно, при это имея такую схему измерения температуры, совершенно не важно насколько точны датчики, т.к. физику не обманешь. То что все показания примерно одинаковы видно на скринах меню аппарата.

В скетче категорически нельзя пользоваться паузой в виде delay(ms). Все паузы будем обрабатывать с помощью таймера millis(). Достаточно громоздко, но результат требуемый и не надо разбираться с библиотеками.

С опросом датчиков у меня получилось... как получилось)))... но работает, кто сделает проще и лучше - буду признателен. На опрос 3х датчиков уходит 2250 мс. Для работы необходимо указать адреса подключенных датчиков. Для этого необходимо загрузить из примеров Oneware > DS18x20_Temperature, этот скетч выдаст все адреса подключенных датчиков. Эти адреса необходимо записать в byte addr1...3[8].

Отдельно скетч опроса 3х датчиков dallas DS18B20:

#include <OneWire.h>
  int flagTemp = 0;           // переменная отслеживания опроса датчика температуры
  OneWire ds (14);
  byte data[12];
  byte addr1[8] = {0x28, 0xFF, 0xDC, 0xC8, 0x70, 0x16, 0x04, 0xDA};  //сухопарник выход
  byte addr2[8] = {0x28, 0xFF, 0xE9, 0xCF, 0x70, 0x16, 0x04, 0x0A};  //сухопарник вход
  byte addr3[8] = {0x28, 0xFF, 0xAB, 0x23, 0x62, 0x16, 0x03, 0xBD};  //куб
  unsigned int raw;
  float temp1, temp2, temp3;
  int thisAddr = 0;
  unsigned long AllTime;      // переменная для записи времени прошедшего со старта программы
  unsigned long MemTime;      // переменная для записи времени начала события относительно времени прошедшего с начала программы
  unsigned long AllTempTime;     // переменная для записи времени прошедшего со старта программы для опроса DALLAS DS18 (температуры)
  unsigned long MemTempTime;     // переменная для записи времени начала события относительно времени прошедшего с начала программы для опроса DALLAS DS18 (температуры)
  int tTempTemp = 0;           // временная переменная для записи расчета времени с последнего опроса датчика, не совсем верно, но по коду можно разобраться зачем)))
  void setup() {
    Serial.begin  (9600);
  }
    //функция опроса датчика часть 1 (до паузы)
    float DS18B20_1(byte *adres){
    ds.reset();
    ds.select(adres);
    ds.write(0x44);
  }
 
  //функция опроса датчика часть 2 (после паузы)
  float DS18B20_2(byte *adres){
    ds.reset();
    ds.select(adres);
    ds.write(0xBE); // Read Scratchpad
    for (byte i = 0; i < 9; i++) { // we need 9 bytes
      data[i] = ds.read ();
    }
    raw =  (data[1] << 8) | data[0];//=======Пересчитываем в температуру
    float celsius =  (float)raw / 16.0;
    return celsius;
  }
  //функция назначения номера опрашиваемого датчика
void AddrAssign(){
  thisAddr = thisAddr+1;
      if (thisAddr>3){
        thisAddr=1;
      }
  }
  void loop(){
  if (flagTemp == 0){
  AddrAssign();
  flagTemp = 1;              // выставляем флаг на пропуск этого блока, пока не пройдет 750 мс
  if(thisAddr == 1){
  DS18B20_1(addr1);
  }else if(thisAddr == 2){
  DS18B20_1(addr2);
  }else if(thisAddr == 3){
  DS18B20_1(addr3);
  }
  MemTempTime = millis();   // засекаем время
    }else{
      AllTempTime = millis();  //
      tTempTemp = AllTempTime - MemTempTime;
      if (tTempTemp < 750){
       flagTemp = 1;
    }else{
  flagTemp = 0;
  if(thisAddr == 1){
  temp1=DS18B20_2(addr1);
  Serial.print("temp1 ");
  Serial.println(temp1);
  }else if(thisAddr == 2){
  temp2=DS18B20_2(addr2);
  Serial.print("temp2 ");
  Serial.println(temp2);
  }else if(thisAddr == 3){
  temp3=DS18B20_2(addr3);
  Serial.print("temp3 ");
  Serial.println(temp3);
  Serial.println("---------------------------------------");
  }

  }
  }
  }

Полный скетч аппарата внизу страницы.

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

Dallas18b20

 

Итак, самое интересное, график выхода в рабочее состояние при тестовой перегонке, цель которой было получение значений для дальнейшего "ологичивания" процесса, ну и управление конечно, на данной перегонке, в ручном режиме.

graffik 3dallas

На графике видно прохождение пара от перегонного куба до холодильника в зависимости от указанных выше температур. Здесь видно, что после 54 градусов на входе в сухопарник можно начинать бдить и потихоньку уменьшать мощность для более плавного выхода на рабочий режим,ориентироваться будем не на величину температуры, а на разницу в показании этих двух датчиков, но привязаться к показанию датчика на входе в сухопарник все-таки необходимо, скажем: температура больше 50 и разница в 24 градусов => уменьшаем мощность.

 

Определим режимы работы тена по увеличению соотношения нагрев/пауза, больше соотношение - больше нагрев:

2/5=0.4

3/7=0.43

2/4=0.5

4/7=0.57

3/5=0.6

2/3=0.6(6)333331

3/4=0.75

4/5=0.8

2/2=1

4/3=1.3(3)

3/2=1.5

4/2=2

Составляем два массива соответствия значения нагрева/паузы к величине режима нагрева:

WarmUp {2,3,2,4,3,2,3,4,2,4,3,4}

WarmDown {5,7,4,7,5,3,4,5,2,3,2,2}

Теперь мы сможем пользоваться 12ю режимами.

Время нагрева/паузы в программе будем получать так:

Если tU - время нагрева, tD - время паузы, а WarmMode - режим нагрева (начение от 0 до 11):

tU=WarmUp[WarmMode]

tD= WarmDown[WarmMode]

Внесены изменения в скетч - аппарат отработал перегонку в автоматическом режиме "на ура"! Это просто невероятно!)))

Температуры в "рабочем режиме" :

температуры самогонного аппарата в режиме перегонки

Может это покажется и немного, в настоящий момент аппарат умеет сам, полностью на всех этапах, управлять температурным режимом перегонки и завершить ее по заданной температуре в кубе. Контроль плавный, четкий и беспристрастный. Остается открытым вопрос о смене (скорей просто сливе содержимого) сухопарника и замены емкости под сэм после отбора голов. Эти проблемы не программные, а чисто механические. Необходимо сделать девайсы, которыми надо будет управлять или получать от них информацию о состоянии. Скажу больше: в настоящий момент уже сложно разобраться в проводках от ардуины к промежуточной плате и переферии, так что назрела необходимость в очередной модернизации в виде создания печатной платы, которая, кстати, будет легко повторяемой. Начало положено, компоненты заказаны, плата потихоньку рисуется:

плата-шилд автоматизации самогонного аппарата для ардуино

Все что касается дальнейшей модернизации этой версии будет тут же, новой темы создавать не буду.

Итак мысли:

  1.  Разделить автоматический режим на 2 вида: первая перегонка и вторая перегонка.
  2. первая перегонка включает в себя функции переключения ёмкости сбора голов на ёмкость сбора тела СС по достижению заданного уровня голов, периодический слив сухопарника (в т.ч. после отбора голов), а также завершение перегонки по достижению определенной температуры в кубе.
  3. вторая перегонка включает в себя функции переключения ёмкости сбора голов на ёмкость сбора тела СС по достижению заданного уровня голов, периодический слив сухопарника (в т.ч. после отбора голов), а также завершение перегонки по достижению спиртуозности в струе 40%.  заключается в управлении бражной колонной через узел отбора с помощью сервопривода. Ну и конечно вывод в рабочий режим и завершение перегонки. Прошивка периодически допиливается, к сожалению крайне медленно.

 Отследить уровень жидкости при отборе голов просто - банальный поплавок и концевик. Отследить достижение спиртуозности в струе также можно с помощью попугая и спиртометра тем же методом. Переключить направление движения жидкости тоже не проблема - необходим тройник и изменение угла его наклона. Дело за малым))).

Вывод из всего вышесказанного: дополнительно понадобятся два концевика и два серва. Также хочу заложить разъем соединения со второй платой ардуино, с целью передачи/получения данных о температуре/времени, с их последующим сбором и сохнанением "куда-нибудь". Либо на SD, либо следствами связки веб-сервер - компьютер. Это мне будет необходимо для реализации следующего проекта "недоректификат" или "передистиллят", идея которого описана в конце страницы.

 


Скетч:

#include <OneWire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
OneWire  ds(14);
Adafruit_PCD8544 display = Adafruit_PCD8544(7, 6, 5, 4, 3);
int thisAddr = 0; //переменная для определения опрашиваемого датчика dallas
byte data[12];
byte addr1[8] = {0x28, 0xFF, 0xDC, 0xC8, 0x70, 0x16, 0x04, 0xDA};  //сухопарник выход
byte addr2[8] = {0x28, 0xFF, 0xE9, 0xCF, 0x70, 0x16, 0x04, 0x0A};  //сухопарник вход
byte addr3[8] = {0x28, 0xFF, 0xAB, 0x23, 0x62, 0x16, 0x03, 0xBD};  //куб
unsigned int raw; //относится к измерению температуры
float Temp, Temp1, Temp2;  // температуры датчиков
int flagTemp = 0;           // переменная отслеживания опроса датчика температуры
int tTempTemp = 0;           // временная переменная для записи расчета времени с последнего опроса датчика, не совсем верно, но по коду можно разобраться зачем))))
int WarmDevice = 13;  //тэн подключен к 13му пину
int FanDevice = 12;  //вентилятор подключен к 12му пину
int xPin = A5;  // пин джойстика
int yPin = A4;  // пин джойстика
int buttonPin = 2;  //кнопка (средняя) на джойстике
int xPosition = 0;  // данные о состоянии джойтика по оси Х
int yPosition = 0;  // данные о состоянии джойтика по оси У
int buttonState = 0;  // состояние кнопки
int tD;
int tU;
unsigned long tDr = 0;
unsigned long tUr = 0;
int x1 = 0;
int y1 = 0;
int Xmax = 3; //количество разделов меню (0...Xmax)
// *** массивы
char* Message0[] = {"OFF","ON"};
char* Message1[] = {"FP","PC","AM"};
int Ymax[] = {1,2,11,1}; //массив количество подпунктов меню (зависит от раздела меню (Xmax) и определяется в ф-ии MinMax_Y())
int Ymem[] = {0,0,2,0};  //массив для запоминания позиции У для раздела меню Х (yx0,yx1...yx4), Ymem[2,3] значения первоначального режима нагрева +2
  int WarmUp[] = {2,3,2,4,3,2,3,4,2,4,3,2};  //массив зависимости значений нагрева ТЭНа от номера режима
int WarmDown[] = {5,7,4,7,5,3,4,5,2,3,2,1};  //массив зависимости значений пауз в работе ТЭНа от номера режима
//--- массивы
int FlagJoystikTouch = 0; //переменная для определения доступности переключения кнопок джойтика (0 - доступно; 1 - джойстик заблокирован)
unsigned long tTochJDelta;  //переменная для тестирования, по идее ненужна
unsigned long tTochJ = 500; //время задержки в переключении кнопок джойстика в милисекундах
unsigned long AllTimeJ; //этой переменой отслеживаем время с момента срабатывания кнопок джойтика AllTimeJ-MemTimeJ и сравниваем с tTochJ
unsigned long MemTimeJ; //в эту переменную пишем(засекаем) время срабатывания кнопок джойтика с момента запуска программы
unsigned long AllTime;      // переменная для записи времени прошедшего со старта программы
unsigned long MemTime;      // переменная для записи времени начала события относительно времени прошедшего с начала программы
unsigned long AllTempTime;     // переменная для записи времени прошедшего со старта программы для опроса DALLAS DS18 (температуры)
unsigned long MemTempTime;     // переменная для записи времени начала события относительно времени прошедшего с начала программы для опроса DALLAS DS18 (температуры)
int OnOff = 0;  // переменная общего вкл/выкл
int ModeVar = 0;  // переменная указывающая режим работы (full power, power control, automatic)
float TempEnd = 98.1;
int TempUpSlow = 0;
float ThisTempVar;  // соотношение температуры tU/tD для отслеживания включения вентилятора
int ProcEndTemp = 0;
//***бипер+кнопка контроля переполнения емкости
int BeeperPin = 10;
int FlagBeeper = 0;
int tBeeperDelta = 0;
unsigned long MemTimeBeeper;
unsigned long AllTimeBeeper;
int ControlSamPin = A3;  //контакты контроля переполнения емкости
//--- конец бипер+контакты контроля переполнения емкости



  // *** НАЧАЛО ФУНКЦИЙ

  // *** фунцция биппера
  void BeeperOneBeep(int UpBeepValue, int UpBeepTime, int DownBeepValue, int DownBeepTime){   //функция BeeperOneBeep(1,500,0,5000);
  if(FlagBeeper == 0){  //если вызвали функцию
  MemTimeBeeper = millis();
  FlagBeeper = 1;
   }else if(FlagBeeper == 1){  //если функция вызвана и время засечено
    AllTimeBeeper = millis();  //  смотрим прошедшее время с момента запуска программы
    analogWrite(BeeperPin, UpBeepValue);
    if (UpBeepTime > AllTimeBeeper-MemTimeBeeper){  //вычисляем разницу между временем и сравниваем с необходимой задержкой
      tBeeperDelta = AllTimeBeeper-MemTimeBeeper;  //просто так считаем
    }else{  //если время паузы больше чем необходимо
      FlagBeeper = 2; //переходим от Up к Down на пине бипера
      tBeeperDelta = 0;  //просто так обнуляем переменную (это необязательно)
    }
  }else if(FlagBeeper == 2){  //если бипер отпищал свое время
    AllTimeBeeper = millis();  //  смотрим прошедшее время с момента запуска программы
    analogWrite(BeeperPin, DownBeepValue);
    if (DownBeepTime > AllTimeBeeper-MemTimeBeeper){  //вычисляем разницу между временем и сравниваем с необходимой задержкой
      tBeeperDelta = AllTimeBeeper-MemTimeBeeper;  //просто так считаем
    }else{  //если время паузы больше чем необходимо
      FlagBeeper = 0; //закончили работу функции
      tBeeperDelta = 0;  //просто так обнуляем переменную (это необязательно)
    }
  }
}

  // *** функция отслеживания времени блокировки клавиш джойстика
    
 void JoystikPauseTime(){   //функция отслеживания времени блокировки клавиш джойстика
  if(FlagJoystikTouch == 1){  //если нажалась клавиша джойстика
    AllTimeJ = millis();  //  смотрим прошедшее время с момента запуска программы
    if (tTochJ > AllTimeJ-MemTimeJ){  //вычисляем разницу между временем нажатия и настоящим и сравниваем с необходимой задержкой
      tTochJDelta = AllTimeJ-MemTimeJ;  //просто так считаем
    }else{  //если время паузы больше чем необходимо
      FlagJoystikTouch = 0; //разблокируем клавиши джойстика
      tTochJDelta = 0;  //просто так обнуляем переменную (это необязательно)
    }
  }
 }
  // --- конец функции отслеживания времени блокировки клавиш джойстика

  // ***функция ограничения значения Х1
  
  void MinMax_X(){
  if (x1 < 0){
    x1 = 0;
    Ymem[x1]=y1;
  }else if(x1 > Xmax){
    x1 = Xmax;
    Ymem[x1]=y1;
  }
  }
  // ---конец функции ограничения значения Х1

  // ***функция ограничения значения У1
  
  void MinMax_Y(){
  if (y1 < 0){
    y1 = 0;
    Ymem[x1]=0;
  }else if(y1 > Ymax[x1]){
    y1 = Ymax[x1];
    Ymem[x1]=y1;
  }
  }
  // ---конец функции ограничения значения У1

    //***функция опроса датчика часть 1 (до паузы)
    float DS18B20_1(byte *adres){
    ds.reset();
    ds.select(adres);
    ds.write(0x44); // start conversion, with parasite power on at the end
  }
  
  //***функция опроса датчика часть 2 (после паузы)
  float DS18B20_2(byte *adres){
    ds.reset();
    ds.select(adres);
    ds.write(0xBE); // Read Scratchpad
    for (byte i = 0; i < 9; i++) { // we need 9 bytes
      data[i] = ds.read ();
    }
    raw =  (data[1] << 8) | data[0];//=======Пересчитываем в температуру
    float celsius =  (float)raw / 16.0;
    return celsius;
  }
  
  //*** функция назначения номера опрашиваемого датчика
void AddrAssign(){
  thisAddr = thisAddr+1;
      if (thisAddr>3){
        thisAddr=1;
      }
  }
// ***ф-ия контроля вентилятора в зависимости от режима нагрева
   void FanControlPower(){
      ThisTempVar =(float)tU/(float)tD;
    if(ThisTempVar >= 0.65){   // значение соотношения температуры tU/tD для включения вентилятора
    digitalWrite(FanDevice, !digitalRead(FanDevice)); //при 5 вольтах все равно большие обороты, поэтому включаем через цикл
    }else{
    digitalWrite(FanDevice, 0);
  }
  }
  //*** ф-ия вкл\выкл на tU tD ТЭНА
 void WarmControlPause(){
if (TempUpSlow == 0){                     //цикл вкл\выкл не начался
    TempUpSlow = 1;      // говорим что запустили плавный режим
    MemTime = millis();   // засекаем время
    digitalWrite(WarmDevice, 0);
//###########################################    
    }else{
      AllTime = millis();  // 
      if(tD > AllTime-MemTime){
        tUr = 0;
        tDr = AllTime-MemTime;
        digitalWrite(WarmDevice, 0);                   // выключаем реле
//###########################################      
      }else if(tU > AllTime-MemTime-tD){
        tDr = 0;
        tUr = AllTime-MemTime-tD;
      digitalWrite(WarmDevice, 1);
//###########################################      
      }else{
      TempUpSlow = 0;
      digitalWrite(WarmDevice, 0);
    }
  }
 }
  // --- КОНЕЦ ФУНКЦИЙ


  void setup()
{
  //TCCR2B = TCCR2B & 0b11111000 | 0x01;
  display.begin(); // инициализируем дисплей
  display.setContrast(60);  // устанавливаем контраст LCD
  display.clearDisplay();   // очищаем экран
  pinMode(WarmDevice, OUTPUT);
  digitalWrite(WarmDevice, 0);
  digitalWrite(9, 0);
  pinMode(FanDevice, OUTPUT);
  digitalWrite(FanDevice, 0);
  pinMode(xPin, INPUT); // пин джойстика на input
  pinMode(yPin, INPUT); // пин джойстика на input
  pinMode(buttonPin, INPUT_PULLUP); // активируем подтягивающий резистор на пине кнопки
 // pinMode(ControlSamPin, INPUT);
}

void loop()
{

  //BeeperOneBeep(100,150,0,5000);
  
    // *** НАЧАЛО ТЕМПЕРАТУРА

      if (flagTemp == 0){
  AddrAssign();
  flagTemp = 1;              // выставляем флаг на пропуск этого блока, пока не пройдет 1000 мс
  if(thisAddr == 1){
  DS18B20_1(addr1);
  }else if(thisAddr == 2){
  DS18B20_1(addr2);
  }else if(thisAddr == 3){
  DS18B20_1(addr3);
  }  
  MemTempTime = millis();   // засекаем время
    }else{
      AllTempTime = millis();  // 
      tTempTemp = AllTempTime - MemTempTime;
      if (tTempTemp < 750){
       flagTemp = 1;
    }else{
  flagTemp = 0;
  if(thisAddr == 1){
  Temp2=DS18B20_2(addr1);
  Serial.print("Temp2 "); 
  Serial.println(Temp2);
  }else if(thisAddr == 2){
  Temp1=DS18B20_2(addr2);
  Serial.print("Temp1 "); 
  Serial.println(Temp1);
  }else if(thisAddr == 3){
  Temp=DS18B20_2(addr3);
  Serial.print("Temp "); 
  Serial.println(Temp);
  Serial.println("---------------------------------------");
  }
  }
  }

  // *** КОНЕЦ ТЕМПЕРАТУРЫ
  xPosition = analogRead(xPin); //определяем позицию джойстика по Х
  yPosition = analogRead(yPin); //определяем позицию джойстика по У
  buttonState = digitalRead(buttonPin); //определяем состояние кнопки на джойстике
  // *** КПОПКИ ДЖОЙСТИКА
  
  // *** позиция джойстика по X
  if (xPosition > 700 && FlagJoystikTouch == 0){  //среднее положение джойстика 510. тут и далее определяем неслучайное нажатие на клавишу джойстика, при этом клавиша должна быть разблокирована функцией JoystikPauseTime()
    Ymem[x1]=y1;  // запоминаем значение У1 для Х1
    x1 = x1 + 1;  //увеличаваем значение X1
    MinMax_X(); // функция ограничения значения X1 (0...Xmax)
    y1 = Ymem[x1]; // читаем знаение Y1 для данного X1 и выставляем значение У1 от изменненного Х1
    FlagJoystikTouch = 1; //блокируем клавиши джойстика
    MemTimeJ = millis();  //засекаем время нажатия
  }else if (xPosition < 450 && FlagJoystikTouch == 0){
    Ymem[x1]=y1;  // запоминаем значение У1 для Х1
    x1 = x1 - 1;  //уменьшаем значение X1
    MinMax_X(); // функция ограничения значения X1 (0...Xmax)
    y1 = Ymem[x1]; // читаем знаение Y1 для данного X1 и выставляем значение У1 от изменненного Х1
    FlagJoystikTouch = 1;
    MemTimeJ = millis();
  }else{
    JoystikPauseTime();
  }
  // --- конец позиция джойстика по X
  // *** позиция джойстика по Y
    if (yPosition < 470 && FlagJoystikTouch == 0){
    y1 = y1 + 1;
    MinMax_Y();
    Ymem[x1]=y1;  // запоминаем значение У1 для Х1
    FlagJoystikTouch = 1;
    MemTimeJ = millis();
  }else if (yPosition > 700 && FlagJoystikTouch == 0){
    y1 = y1 - 1;
    MinMax_Y(); // функция ограничения значения Y1 (0...Ymax)
    Ymem[x1]=y1;  // запоминаем значение У1 для Х1
    FlagJoystikTouch = 1;
    MemTimeJ = millis();
  }else{
    JoystikPauseTime();
  }
  // --- конец позиция джойстика по Y
  
  // --- КОНЕЦ КПОПКИ ДЖОЙСТИКА

  // *** УПРАВЛЕНИЕ ТЭНОМ
  
  tU=(WarmUp[Ymem[2]])*100; //считаем промежуток включения тэна в зависимости от режима Ymem[3]
  tD=(WarmDown[Ymem[2]])*100; //считаем промежуток выключения тэна в зависимости от режима Ymem[3]
  OnOff = Ymem[0];  //***/ используем старый, отработанный кусок программы, 
  ModeVar = Ymem[1];  // поэтому приводим все к тем же переменным /***
//=======================================================================================================
if (OnOff == 0){                // если выключено, то нас больше ничего не интересует - все выключено
  digitalWrite(WarmDevice, 0);
  digitalWrite(FanDevice, 0);
  BeeperOneBeep(20,50,0,5000);
//=======================================================================================================
}else if (OnOff != 0){          // если включено - смотрим что дальше
if (ModeVar == 0){              // режим постоянного нагрева
    digitalWrite(WarmDevice, 1);
    digitalWrite(FanDevice, !digitalRead(FanDevice)); //при 5 вольтах все равно большие обороты, поэтому включаем через цикл
//=======================================================================================================
}else if(ModeVar == 1){         // режим ручного регулирования нагрева
  {
    FanControlPower();
    WarmControlPause();
//###########################################
  }
//=======================================================================================================
}else if(ModeVar == 2){     // автоматический режим работы

if (Temp1 <= 50.0 && Temp < TempEnd){                    // автоматический режим постоянный нагрев до 50 град
digitalWrite(WarmDevice, 1);
digitalWrite(FanDevice, !digitalRead(FanDevice));
}else if (Temp1 > 50.0 && Temp < TempEnd){                    // автоматический режим управление после 50 град
if ((Temp1-Temp2)>=20.0){
digitalWrite(WarmDevice, 1);
digitalWrite(FanDevice, !digitalRead(FanDevice));
  }
else if ((Temp1-Temp2)<20.0 && (Temp1-Temp2)>=7.0){
  Ymem[2]=8;
  FanControlPower();
  WarmControlPause();
  }
else if ((Temp1-Temp2)<7.0 && (Temp1-Temp2)>=5.0){
  Ymem[2]=5;
  FanControlPower();
  WarmControlPause();
  }
else if ((Temp1-Temp2)<5.0 && (Temp1-Temp2)>=3.0){
  Ymem[2]=4;
  FanControlPower();
  WarmControlPause();
  } 
else if ((Temp1-Temp2)<3.0){
  Ymem[2]=3;
  FanControlPower();
  WarmControlPause();
  }
}else if (Temp1 > 50.0 && Temp >= TempEnd){                    // выключаем все при температуре TempEnd
digitalWrite(FanDevice, 0);
OnOff = 0;
Ymem[0] = 0;
y1 = 0;
digitalWrite(WarmDevice, 0);
}
}  
}
//=======================================================================================================

//тест
if(ProcEndTemp == 1){
  BeeperOneBeep(150,100,0,5000);
  }


  // --- УПРАВЛЕНИЕ ТЭНОМ
  
  // *** дисплей. у нас есть 5 строчек.
  display.clearDisplay();
  display.setTextSize(1);
  display.print(" KUB  ");
  display.println(Temp);
  //display.print("|");
  display.print(Temp1);
  display.print(" | ");
  display.println(Temp2);
  if(digitalRead(WarmDevice)==0){
  display.println("      |  DOWN");
  }else if(digitalRead(WarmDevice)==1){
  display.println(" UP   |      ");
  }
  display.print(Message0[Ymem[0]]);
  display.print(" | ");
  display.print(Message1[Ymem[1]]);
  display.print(" |");
  if((OnOff != 0 && ModeVar == 2 && Temp1 <= 50.0 && Temp < TempEnd) | (OnOff != 0 && ModeVar == 0)){
  display.println("full");
  }else if(OnOff != 0 && ModeVar == 2 && Temp1 > 50.0 && Temp < TempEnd && (Temp1-Temp2)>=20.0){
  display.println("full");
  }else{
  display.print(tU/100);
  display.print("/");
  display.println(tD/100);
  }
  display.drawLine(0, 35, 84, 35, BLACK);
  display.println(" ");
  
  if (x1==0){
    if (y1==0){
    display.println("POWEROFF");
  }else if(y1==1){
    display.println("POWER ON");
  }
  }else if(x1==1){
  if(y1==0){
    display.println("FULL POWER");
  }else if(y1==1){
    display.println("POWER CONTROL");
  }else if(y1==2){
    display.println("AUTO MODE");
  }
  }else if(x1==2){
    display.print("Warm Mode   ");
    display.println(Ymem[2]);
  }
  else if(x1==3){
    if (Ymem[3] == 1){
    digitalWrite(FanDevice, 1);
    display.println("FAN -- UP");
    }else{
    digitalWrite(FanDevice, 0);
    display.println("FAN -- DOWN");
    }
    display.println(tD);
  }
  display.display();
}

 

Блютуз терминал доступен и легко подключаем даже для WINDOWS10, не говоря уже про android. Выглядит это вот так:

блютуз терминал для windows10

 

Следующая идея для реализации - "недоректификат" или "передистиллят".

Идея была подсказана Станиславом, за что ему отдельное огромное спасибо, надеюсь он и дальше будет поддерживать эту тему своими знаниями.

Идея:

поддерживать в царге (скорей будем ориентироваться на верхнюю точку) постоянную температуру кипения спирта. Изменение температуры будем пытаться прогнозироть по нижним двум датчикам. Все что имеет меньшую температуру должно выпадать конденсатом непосредственно обратно в куб. Идея есть, мат. обеспечение на стадии сбора. На рисунке - ахинея))) До сбора испытательного стенда и получения данных для анализа - разговаривать можно чисто теоретически. Если кому будет интересно обсуждение этого проекта и/или участие в нем - пишите в комментарии - создадим тему на форуме.

схема автоматизации царги

Эксперимент так и остался всего лишь экспериментом, но выполненным))).

крепость на второй перегонке (первый литр)