Автоматика на ARDUINO V.4 - DISTSYS.RU
23 января, Суббота, 2021

Login Form

Breadcrumbs

Автоматика на ARDUINO V.4

 

Нужна ли автоматика для самогонного аппарата?
  • Голоса: (0%)
  • Голоса: (0%)
  • Голоса: (0%)
Всего голосов:
Первый голос:
Последний голос:
 
 

 

 

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


 В предыдущих сериях: Первые потуги на поприще автоматизации | Автоматика для самогонного аппарата на ARDUINO V.2 | Новое исполнение и компановка автоматики на ARDUINO V.3


 

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

   Этот скетч и скриптик я написал еще год назад, вроде оттестил, но никак времени не хватает попробовать на перегонке. Концепция следующая: скрипт разово отправляет строкой все параметры в ардуину, через ком-порт, ардуина распирсивает вывод и меняет свои переменные на полученные. В свое время ардуина отдает через каждых сколько-то секунд все параметры, которые у нее в работе, компу, который также все распарсивает и показывает что происходит. Все параметры при получении записываются в текстовый файлик. Есть вариант с sqlite3. Есть возможность включить постоянное получение данных со стороны компа.

   Повторюсь, что времени на "покопаться" сайчас нет от слова совсем. В данном варианте не предусмотрено управление джойстиком и экранчик тоже удран совсем. Вся переферия подключается также как и здесь версия3.

   GUI для управления выглядит так:

gui для автоматики самогонного аппарата

   Просьба сильно не пинать, выкладываю недоделку исключительно по просьбе.

 

 

 Скетч для ардуино

 

#include <OneWire.h>

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 TempUpSlow = 0;
unsigned long tDr = 0;
unsigned long tUr = 0;
int WarmDevice = 13;  //тэн подключен к 13му пину
int tD;
int tU;
float ThisTempVar;
int flagTemp = 0;           // переменная отслеживания опроса датчика температуры
OneWire ds (14);
byte data[12];
byte addr1[8] = {0x28, 0xFF, 0x82, 0x03, 0xB4, 0x16, 0x03, 0x0E};  //сухопарник выход
byte addr2[8] = {0x28, 0xFF, 0x56, 0x5A,0xB4, 0x16, 0x03, 0x04};  //сухопарник вход
byte addr3[8] = {0x28, 0xFF, 0xFF, 0x56,0xB4, 0x16, 0x03, 0xD6};  //куб
unsigned int raw;
float T1, T2, T3;
int thisAddr = 0;
unsigned long AllTime;      // переменная для записи времени прошедшего со старта программы
unsigned long MemTime;      // переменная для записи времени начала события относительно времени прошедшего с начала программы
unsigned long AllTempTime;     // переменная для записи времени прошедшего со старта программы для опроса DALLAS DS18 (температуры)
unsigned long MemTempTime;     // переменная для записи времени начала события относительно времени прошедшего с начала программы для опроса DALLAS DS18 (температуры)
int tTempTemp = 0;           // временная переменная для записи расчета времени с последнего опроса датчика, не совсем верно, но по коду можно разобраться зачем)))
//int Ttmp;
int FanDevice = 12;  //вентилятор подключен к 12му пину
float Tend = 36.60; // тестовая температура
char incoming;
char str[6];
String str1;
int i;
int OnOff = 0; // переменная On/Off
int Mode = 0; // переменная full/manual/auto
int AMode = 1; // переменная обозначающая номер режима нагрева
int FlagSerial = 0;
//byte data[12];
//unsigned long data = 0;
unsigned long TimeSendSerial = 500;
unsigned long MemTimeSerial = 0;
unsigned long RunTimeSerial = 0;

// === START FUNCTIONS ===
void DataSendSerial () {
  if (FlagSerial == 0) {
    FlagSerial = 1;
    MemTimeSerial = millis();
  } else if (FlagSerial == 1) {
    RunTimeSerial = millis();
    if (TimeSendSerial < RunTimeSerial - MemTimeSerial) {
      Serial.print(":");
      Serial.print(OnOff);
      Serial.print(Mode);
      Serial.print(AMode);
      Serial.print(int(Tend*10));
      Serial.print(int(T1*100));
      Serial.print(int(T2*100));
      Serial.print(int(T3*100));
      Serial.println(";");
//      Serial.print(Temp2);
//      Serial.println(Temp1);
      FlagSerial = 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 FanControlPower(){
  ThisTempVar =(float)tU/(float)tD;
  if(ThisTempVar >= 0.5){   // значение соотношения температуры tU/tD для включения вентилятора
  digitalWrite(FanDevice, 1); //при 5 вольтах все равно большие обороты, поэтому включаем через цикл
  }else{
  digitalWrite(FanDevice, 0);
}
}

// === END FUNCTIONS ===


void setup(){
Serial.begin(9600); //initialize serial COM at 9600 baudrate
pinMode(WarmDevice, OUTPUT); //make the LED pin (13) as output
digitalWrite (WarmDevice, 0);
pinMode(FanDevice, OUTPUT);
digitalWrite(FanDevice, 0);
}


    //функция опроса датчика часть 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() {

DataSendSerial();

// === Start SerialAvailable ===

while (Serial.available())
{
incoming = Serial.read();
//Serial.println(incoming);
if (incoming < '0' || incoming > '9')break;
{
str[i] = incoming;
Serial.print(i);
Serial.print("==>>");
Serial.println(str[i]);
i++;
}
if (i > 5) //на данные отводим 6 знака
{
str[i] = 0;
i=0;
}
}
OnOff = int(str[0]- '0');
Mode = int(str[1]- '0');
AMode = int(str[2]- '0');
str1 = "";
for (int i = 3; i < 6; i++){

str1 = str1 + (str[i]- '0');

   }

// === End SerialAvailable ===

    // *** НАЧАЛО ТЕМПЕРАТУРА
 
      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){
  T1=DS18B20_2(addr1);
//  Serial.print("T1 "); 
//  Serial.println(T1);
  }else if(thisAddr == 2){
  T2=DS18B20_2(addr2);
//  Serial.print("T2 "); 
//  Serial.println(T2);
  }else if(thisAddr == 3){
  T3=DS18B20_2(addr3);
//  Serial.print("T3 "); 
//  Serial.println(T3);
//  Serial.println("---------------------------------------");
  }
  }
  }
 
  // *** КОНЕЦ ТЕМПЕРАТУРЫ

Tend = (float)str1.toInt()/(float)10;
//T = String (T1)+String (T2)+String (T3);
//Serial.print(":");
//Serial.println(incoming);


// *** УПРАВЛЕНИЕ ТЭНОМ
   
  tU=(WarmUp[AMode])*100; //считаем промежуток включения тэна в зависимости от режима Ymem[3]
  tD=(WarmDown[AMode])*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 (Mode == 0){              // режим постоянного нагрева
    digitalWrite(WarmDevice, 1);
//    digitalWrite(FanDevice, !digitalRead(FanDevice)); //при 5 вольтах все равно большие обороты, поэтому включаем через цикл
      digitalWrite(FanDevice, 1);
//=======================================================================================================
}else if(Mode == 1){         // режим ручного регулирования нагрева
  {
    FanControlPower();
    WarmControlPause();
//###########################################
  }
//=======================================================================================================
}else if(Mode == 2){     // автоматический режим работы
 
if (T2 <= 50.0 && T1 < Tend){                    // автоматический режим постоянный нагрев до 50 град
digitalWrite(WarmDevice, 1);
digitalWrite(FanDevice, 1);
}else if (T2 > 50.0 && T1 < Tend){                    // автоматический режим управление после 50 град
if ((T1-T2)>=20.0){
digitalWrite(WarmDevice, 1);
digitalWrite(FanDevice, 1);
  }
else if ((T2-T3)<20.0 && (T2-T3)>=7.0){
  AMode=8;
  FanControlPower();
  WarmControlPause();
  }
else if ((T2-T3)<7.0 && (T2-T3)>=5.0){
  AMode=5;
  FanControlPower();
  WarmControlPause();
  }
else if ((T2-T3)<5.0 && (T2-T3)>=3.0){
  AMode=4;
  FanControlPower();
  WarmControlPause();
  } 
else if ((T2-T3)<3.0){
  AMode=3;
  FanControlPower();
  WarmControlPause();
  }
}else if (T2 > 50.0 && T1 >= Tend){                    // выключаем все при температуре TempEnd
digitalWrite(FanDevice, 0);
OnOff = 0;
AMode = 0;
digitalWrite(WarmDevice, 0);
}
}  
}
//====== --- УПРАВЛЕНИЕ ТЭНОМ =========



//if(OnOff == 1){
//digitalWrite (LED_BUILTIN, HIGH);
//}else{
//  digitalWrite (LED_BUILTIN, LOW);
//}
}

 

 

 GUI для управления с компа

 

 

#! /usr/bin/python3
# coding: utf-8

import time
import serial
import os
from tkinter import *
from tkinter import ttk
from tkinter.ttk import Combobox
import glob


class Box_create():
    def __init__(self, text_lable, list_add, col, row):
        self.text_lable = text_lable
        self.list_add = list_add
        self.col = col
        self.row = row
        lbl = Label(window, text=self.text_lable)
        lbl.grid(column=self.col - 1, row=self.row)
        self.combo = Combobox(window)
        self.combo['values'] = (self.list_add)
        self.combo.current(0)
        self.combo.grid(column=self.col, row=self.row)

    @property
    def print_get(self):
        return self.combo.get()


class Button_create():
    def __init__(self, text_lable, command, col, row):
        self.text_lable = text_lable
        self.command = command
        self.col = col
        self.row = row
        self.button = Button(window)
        self.button['text'] = (self.text_lable)
        self.button['command'] = (self.command)
        self.button.grid(column=self.col, row=self.row)

    @property
    def print_get(self):
        print(self.button.get())
        return self.button.get()

###class lable start
class Lable_create():
    def __init__(self, text_lable, col, row):
        self.text_lable = text_lable
        self.col = col
        self.row = row
        self.lable = Label(window)
        self.lable['text'] = (self.text_lable)
        self.lable.grid(column=self.col, row=self.row)

    def label_text_configure(self, text_lable):
        self.text_lable = text_lable
        self.lable['text'] = (self.text_lable)

    @property
    def print_get(self):
        print(self.lable.get())
        return self.lable.get()


def port_search():
    ports = []
    if sys.platform.startswith('win'):
        ports = ['COM%s' % (i + 1) for i in range(25)]
    elif sys.platform.startswith('linux'):
        ports = glob.glob('/dev/tty[A-Za-z]*')
    else:
        raise EnvironmentError('Unsupported platform')
    return ports

def serial_port_select():
    global serial_port_name
    global serial_port_speed
    serial_port_name = port_search_box.print_get
    serial_port_speed = 9600
    port_select_label.label_text_configure(serial_port_name)
    os.system('sudo chmod 0666 ' + serial_port_name)
    send_data_to_arduino()

def send_data_to_arduino(symb=0):
    out_label.label_text_configure('FAIL')
#    symb = str(re.sub(r'[\(\)\[\]\ ,\.rbn_dictvalues;:\'\\]', "", str(set_mode.values())))
    symb = str(set_mode['onoff']) + str(set_mode['mode']) + str(set_mode['amode']) + str(set_mode['temp'])
    ser = serial.Serial(serial_port_name, serial_port_speed, timeout=5)
    print(serial_port_name, serial_port_speed)
    print('Отправили:', symb)
    symb1=':'
    ser.write(symb.encode())
    for i in range(5000):
        window.update()
        if str(symb1) in str(ser.readline()):
            out_label.label_text_configure('OK')
            print('Получили:', str(ser.readline()))
            resive_data_from_arduino()
            break
        else:
            out_label.label_text_configure('undefined')

def resive_data_from_arduino():
    #data:
    #:001366;
    ser = serial.Serial(serial_port_name, serial_port_speed, timeout=5)
    print(serial_port_name, serial_port_speed)
    symb = str(':')
    for i in range(50):
        window.update()
        if symb in str(ser.readline()):
            out_label.label_text_configure('OK')
            print('Получили:', ser.readline())
            read_data = re.sub(r'[rbn;:\'\\]', "", str(ser.readline()))
            #set_mode_label.label_text_configure(time.strftime("%Y/%m/%d-%H:%M:%S") + ' --- ' + read_data)
            read_serial_data_list.append(read_data)
            read_data_to_file(read_data)
#            read_serial_data_list_label.label_text_configure(read_serial_data_list[-1])
            read_serial_data_list_label.label_text_configure('Вкл/Выкл: ' + str(read_serial_data_list[-1])[0] + ';\nРежим автоматики: ' + str(read_serial_data_list[-1])[1] + ';\nРежим нагрева: ' + str(read_serial_data_list[-1])[2] + ';\nТемпература отключения в кубе: ' + str(read_serial_data_list[-1])[3:5] + ',' + str(read_serial_data_list[-1])[5])
            read_serial_temp_label.label_text_configure('Температура в кубе: ' + str(read_serial_data_list[-1])[6:8] + ',' + str(read_serial_data_list[-1])[8:10] + ';\nТемп до сухопарника: ' + str(read_serial_data_list[-1])[10:12] + ',' + str(read_serial_data_list[-1])[12:14] + ';\nТемп после сухопарника: ' + str(read_serial_data_list[-1])[14:16] + ',' + str(read_serial_data_list[-1])[16] + str(read_serial_data_list[-1])[17])
            break
        else:
            out_label.label_text_configure('Получаем данные')

def read_data_to_file(data_string):
    read_data_file = open('data_string.txt', 'a')
    read_data_file.write('\n' + time.strftime("%Y_%m_%d_%H_%M_%S", time.localtime()) + ':' + data_string)
    read_data_file.close()

def run_select_0():
    global run
    run = '0'
    run_label.label_text_configure(run)
    window.update()
 
def run_select_1():
    global run
    run = '1'
    run_label.label_text_configure(run)
    window.update()
    
def at_func_run():
    global run
    if run == '1':
        global flag_run
        if flag_run == 0:
            flag_run = 1
            global time_to_run
            global start
            start = time.time()
        else:
            if time.time() > start + time_to_run:
                resive_data_from_arduino()
                flag_run = 0
                from_func_run_label.label_text_configure(run)
                window.update() 
            else:
                pass
                window.update()                
    else:
        from_func_run_label.label_text_configure(run)
        window.update()
    window.update()

def run_function_sleep():
    pass

def add_onoff_to_list():
    set_mode.update({'onoff': onoff_select_box.print_get})
    set_mode_label.label_text_configure(set_mode)

def add_mode_to_list():
    set_mode.update({'mode': mode_select_box.print_get})
    set_mode_label.label_text_configure(set_mode)

def add_amode_to_list():
    set_mode.update({'amode': amode_select_box.print_get})
    set_mode_label.label_text_configure(set_mode)

def add_temp_to_list():
    set_mode.update({'temp': temp_select_box.print_get})
    set_mode_label.label_text_configure(set_mode)

#всякая херня скопом
global serial_port_name
serial_port_name = ''
read_serial_data_list = [] #in resive_data_from_arduino() append
set_mode = {'onoff': 1,  'mode': 2, 'amode': 0, 'temp': 987}
onoff = [0, 1]
mode = [0, 1, 2]
temp = []
temp_select = []
run = 0 # относится к запуску по времени функции
flag_run = 0 # относится к запуску по времени функции
start = 0 # в этой переменной засекаем время запуска
time_to_run = 3 # запуск раз в N секунд
for i in range(970, 999): # здесь создаем список для выбора конечной температуры в боксе
    temp.append(i)
    temp_select.append(i/10)
amode_list = []
for i in range(9):
    amode_list.append(i)

    
print(temp)
window = Tk()
window.title("DISTSYS.RU")
window.geometry("850x600")

port_select_label = Lable_create('/serial/port/select', 1, 2)
out_label = Lable_create('output test', 2, 2)
set_mode_label = Lable_create(set_mode, 3, 2)

port_search_box = Box_create('Serial port', port_search(), 1, 3)
port_search_btn = Button_create('Подключиться', serial_port_select, 2, 3)

onoff_select_box = Box_create('On/Off', onoff, 1, 4)
onoff_select_btn = Button_create('Сохранить', add_onoff_to_list, 2, 4)

mode_select_box = Box_create('Режим', mode, 1, 5)
mode_select_btn = Button_create('Сохранить', add_mode_to_list, 2, 5)

amode_select_box = Box_create('Нагрев', amode_list, 1, 6)
amode_select_btn = Button_create('Сохранить', add_amode_to_list, 2, 6)

temp_select_box = Box_create('Конечная\nтемпература', temp_select, 1, 7)
temp_select_btn = Button_create('Сохранить', add_temp_to_list, 2, 7)

temp_resive_data_btn = Button_create('Получить данные', resive_data_from_arduino, 2, 8)
temp_resive_data_btn = Button_create('Отправить данные', send_data_to_arduino, 3, 8)

read_serial_data_list_label = Lable_create('READ SERIAL DATA LIST', 1, 10)
read_serial_temp_label = Lable_create('READ SERIAL DATA LIST TEMPs', 1, 11)

run_label = Lable_create(run, 2, 12)
from_func_run_label = Lable_create(run, 3, 12)

run_label_info = Lable_create('Получение данных', 1, 13)
run_select_btn1 = Button_create('включить', run_select_1, 2, 13)
run_select_btn2 = Button_create('выключить', run_select_0, 3, 13)

n=0
while n == 0:
    at_func_run()

window.mainloop()

 

 

 

Недостаточно прав для комментирования.

automatoc2

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

 

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

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

 

 

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

подробнее


 

промежуточный вариант управление самогонным аппаратомНачало работы над автоматикой, ошибки, варианты управления ТЭНом.

 

Я думал что это будет самое простое, однако время показало обратное)))... Хотя наверено даже наоборот, я все усложнил и на практике оказалось что нужно относится к этому проще. Так как это не ратификационная колонна, а просто дистилятор, то нет необходимости поддерживать постоянную температуру, управляя тэном по милисекундам. Секундными включениями тэна можно добиться постоянного, но неравномерного (что с нашем случае не принципиально) потока пара из перегонного куба.


Итак, был заказан контроллер ARDUINO UNO R3, релюхи, светодиоды, набор сопротивлений разного номинала, и датчик температуры DALLAS 18DS (по моему так). Всего по 2 штуки, чтобы можно было оперативно заменить. После размышлений как доставить температурный датчик в зону пара в перегонный куб, неожиданно на глаза попался стержень от ручки паркер, он идеально подходил для этой цели: тонкие стенки, с одной стороны он цельный( не надо паять, сминать и тд), подходящий по длине и диаметру. Стержень был схвачен, отпилен со стороны пера и рапотрашен.

подробнее