Как стать автором
Обновить

Хочешь вкусного пива, Arduino в помощь

Время на прочтение12 мин
Количество просмотров39K
Данная публикация навеяна другой. Она называется «Контроллер для домашней пивоварни Mega Brewery. Part I» и опубликована за авторством megadenis.

С чего все началось?


Я студент технического ВУЗа. Однажды, сидя в кафе с другом, который тогда учился в медицинском университете, решили открыть бар. Было много идей, которые, в принципе, заслуживали некоторого внимания. Например, танц-пол, который изменяет угол наклона в зависимости от стиля музыки… Но, наряду со всем многообразием идей, так же была еще одна —

… а не сварить ли нам свое пиво?


Через пару недель я сварил пиво, взяв ингредиенты с пивзавода, на котором работал друг отца. Но многие технологические процессы были нарушены, поэтому вместо пива вышло что-то с не очень приятным запахом.

Через несколько лет решил повторить процесс, немного автоматизировав его с помощью Arduino UNO. И вот, что получилось.

Начну с того, что должна делать вся установка.

  • Проверять себя — все ли работает, все ли подключено;
  • Чистить себя;
  • Подготавливаться к процессу пивоварения;
  • Варить пиво в полуавтоматическом режиме;
  • Варить пиво в ручном режиме;
  • Варить пиво в автоматическом режиме (как стиральная машина стирает белье).


Первый пункт в данный момент не реализован. Пока даже мыслей нет, как реализовать.
Второй — тоже не реализован, но в ближайшее время доделаю, только дождусь, когда доставят насосы с ebay.
Третий пункт — достаточно простой.

Подготовка к процессу пивоварения


Сообщение от системы о том, что необходимо залить воду в чан -> программа ждет нажатия клавиши ОК — > программа посылает arduino команду на включение твердотельного реле -> твердотельное реле включает однокиловатный тенн в чане, доводит до температуры 37 градусов, отсылает программе команду, что все готово для варки. Поддерживает температуру 37 градусов.

Хочется, чтобы была проверка на наличие воды, но датчик еще ждет своего отправления от «китайских братьев».

Варка пива в полуавтоматическом режиме


В принципе, несложная процедура:

— Нажимаем в управляющей программе кнопку «Прогрев», остальные управляющие кнопки не активны;
— После прогрева программа выводить сообщение «Все готово, можно варить»;
— Засыпаем ингредиенты, выбираем программу для варки — кнопка «Варка пива» становится активной;
— Нажимаем кнопку «Варка пива», процесс пошел;
— Далее система периодически будет оповещать информационными сообщениями, что делать и когда.

Нужно следовать инструкциям.

Варка пива в ручном режиме


Данный процесс позволяет задавать параметры варки, изменять их в цикле варки. До него еще не дошел.

Варка в автоматическом режиме


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

Закинул ингредиенты в соответствующие лотки, нажал кнопку и… через полтора месяца получил готовое пиво.

Это вкратце о процессе, а теперь перейдем к техническое стороне.

Техническая сторона процесса


Как сказано выше за управляющий микроконтроллер выступает arduino UNO. К нему подключены 2 реле, 2
цифровых термометра DS18B20.

Arduino общается с основной программой через com порт. Т.к. у меня нет шильда реального времени к arduino, пришлось таймеры брать из visual c#. Опыта написания программ у нет, так что, если вдруг у кого есть идеи, критика — почту за честь. Критикуйте, дербаньте, так сказать, что вдруг не нравится.

Вот текст программы на arduino
#include <OneWire.h>

OneWire  ds(8);  // датчики температуры сидят на 8 пине
int reley1 = 13;
int reley2 = 12;
int reley3 = 11;
int reley4 = 10;
int reley5 = 7; //включение тен
int reley6 = 6; // тен на заторном чане

float temp1;
float temp2;

void setup(void) {
  Serial.begin(9600);
  
  pinMode(reley1,OUTPUT);
  pinMode(reley2,OUTPUT);
  pinMode(reley3,OUTPUT);
  pinMode(reley4,OUTPUT);
  pinMode(reley5,OUTPUT);
  pinMode(reley6,OUTPUT);
  digitalWrite(reley1,LOW);
  digitalWrite(reley2,LOW);
  digitalWrite(reley3,LOW);
  digitalWrite(reley4,LOW);
  digitalWrite(reley5,LOW);
  digitalWrite(reley6,LOW);
  
  
  
  
}

void loop(void) {
if (Serial.available()) {
  
  switch (Serial.read()){ 
 case 'i':
 infuz();
 break;

case 'p':
progrev();
break;

case 'a':
avariya();
break;

case 'v':
varka();
break;

case 't':
temperature();
break;

  }
}
}

void varka() {
  digitalWrite(reley6, HIGH);
  while(Serial.read()!='m') {
    temperature();
    if (temp1 >= 52.00)
  digitalWrite(reley6,LOW);
  else 
  digitalWrite(reley6,HIGH);
  }
  
  while(Serial.read()!='n') {
  //digitalWrite(reley6,HIGH);
  temperature();
  if(temp1>= 62.00)
  digitalWrite(reley6,LOW);
  else
  digitalWrite(reley6,HIGH);
  }
  
   while(Serial.read()!='b') {
  //digitalWrite(reley6,HIGH);
  temperature();
  if(temp1 >= 75.00)
  digitalWrite(reley6,LOW);
  else
  digitalWrite(reley6,HIGH);
  }
digitalWrite(reley6,LOW);

while(Serial.read()!='c')

delay(1000);

while(Serial.read()!='x') {
digitalWrite(reley5,HIGH);
temperature();
}
digitalWrite(reley5,LOW);

  }

void infuz() {
  //temperature();
 //Serial.available();
 while (Serial.read()!='s'){ //выключение цикла инфузионной варки
 if (temp<=69.50)
  digitalWrite(reley5,HIGH);
  else
  digitalWrite(reley5,LOW);
 }
 digitalWrite(reley5,LOW);
}


void progrev() {
  while (temp1 <=36.00)
  temperature();
  digitalWrite(reley6,HIGH);
  digitalWrite(reley6,LOW);
  //while (temperature() >40.0)
  //delay(1000);
  Serial.println('s');
}

void avariya(){
  digitalWrite(reley1,LOW);
  digitalWrite(reley2,LOW);
  digitalWrite(reley3,LOW);
  digitalWrite(reley4,LOW);
  digitalWrite(reley5,LOW);
}


void temperature() {
   byte i;
  byte present = 0;
  byte data[12];
  byte addr[8];
  byte zator[8] = {40, 23, 218, 43, 6, 0, 0, 22}; // адрес температурного датчика в заторе
  byte varilka[8] = {40, 255, 240, 115, 59, 4, 0, 234}; //адрес температурного датчика в варильном чане
  float celsius;
 // float temp;
  boolean gde;
  
  if ( !ds.search(addr)) {
    //Serial.println("No more addresses.");
    //Serial.println();
    ds.reset_search();
    delay(250);
   // return;
  }
  
  
 
  

  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
     // return;
  }
  ds.reset();
  ds.select(addr);
  ds.write(0x44, 1);        // start conversion, with parasite power on at the end
  
  delay(840);     // maybe 750ms is enough, maybe not
  // we might do a ds.depower() here, but the reset will take care of it.
  
  present = ds.reset();
  ds.select(addr);    
  ds.write(0xBE);         // Read Scratchpad

  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    
  }
  
  int16_t raw = (data[1] << 8) | data[0];
 
    byte cfg = (data[4] & 0x60);
    
    if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
    else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
    else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
    
  celsius = (float)raw / 16.0;
  
  for(i = 0; i<8; i++) {
  if (addr[i] == zator[i]) 
  gde = true;
  else {
    gde = false;
  break;
  }
  }
  if (gde) {
    Serial.print("t2 "); //temperatura варочника
    temp2 = celsius;
    Serial.print(temp2);
    Serial.println();
  }
  else
  {
 
   Serial.print("t1 "); //температура затора
   temp2 = celsius;
   Serial.print(temp1);
   
 
}   

}


 




Все бы хорошо, но почему-то третий датчик показывает постоянно 85 градусов. Пока не могу сказать, почему. А нужен он для дальнейшей автоматизации — поддержание температуры в холодильнике.

Вкратце опишу, что делает программа.

Программа разделена на подпрограммы, каждая из которых активируется, если на com порту появляется определенный символ. Например, если в порт попадает буква «p», то включается режим «Прогрев». Или же, если «a» — то вызывается подпрограмма avariya() и все отключается. При вызове подпрограммы temperature() данные записываются в глобальные переменные temp1, temp2. Оттуда и попадают в нужные подпрограммы.

В будущем появятся подпрограммы для варки различных сортов и даже самогона.

Теперь, что касается основной программы управления.

Основная программа управления


Она написана на visual studio c#.

Исходный код программы:

Исходный код программы
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;

namespace WindowsFormsApplication1
{
    

    public partial class Form1 : Form
    {
       
// String portnumber;
        SerialPort Port1 = new SerialPort("COM5", 9600);
        int s=0;
        public Form1()
        {
            InitializeComponent();
        }

        /*private const int CP_NOCLOSE_BUTTON = 0x200;
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams myCp = base.CreateParams;
                myCp.ClassStyle = myCp.ClassStyle | CP_NOCLOSE_BUTTON;
                return myCp;
            }
        }*/
        private void Form1_Load(object sender, EventArgs e)
        {
            label2.Text = Port1.PortName;
            Port1.Open();
          
        }
        //Проверка оборудования
        private void button1_Click(object sender, EventArgs e)
        {
            if (Port1.IsOpen == false)
            {
                try
                {
                    //программу можно доработать информация из podrugomu.com/node/987
            
                    Port1.PortName = label2.Text;
                    Port1.Open();
                    Port1.Write("Check");
                    //SerialPort Port2 = new SerialPort("COM4", 9600);
                    //Port2.Open();
                    //label3.Text = Convert.ToString(Port2.ReadByte()); //проверка показаний порта

                   
                    MessageBox.Show("Процес проверки оборудования запущен", "Инфорамационное сообщение");
                    richTextBox1.Text = richTextBox1.Text + "\n" + "Запуск процесса проверки оборудования"+" "+DateTime.Now.ToString("HH:mm");


                    button1.Enabled = false;
                    button2.Enabled = false;
                    button3.Enabled = false;
                  

                    if (Port1.ReadByte() == 1000)
                    {
                        richTextBox1.Text = richTextBox1.Text + "\n" + "Окончание процесса проверки оборудования"+" "+DateTime.Now.ToString("HH:mm");
                        button1.Enabled = true;
                        button2.Enabled = true;
                        button3.Enabled = true;
                       

                        Port1.Close();
                        MessageBox.Show("Конец Цикла проверки оборудования" +" "+ DateTime.Now.ToString("HH:mm"));
                        richTextBox1.SaveFile("CheckLOG.rtf");
                    }
                    }
                catch
                {
                    richTextBox1.Text = richTextBox1.Text + "\n" + "Ошибка процесса проверки оборудования" +" "+ DateTime.Now.ToString("HH:mm");
                    MessageBox.Show("Неверно выбран порт устройства. Процесс проверки не может быть запущен", "Warninig");
                    richTextBox1.SaveFile("log/Check_"+DateTime.Now.ToString("ddMMyyyy")+".rtf");
                }
            }
        }
        // Чистка оборудования
        private void button3_Click(object sender, EventArgs e)
        {
            if (Port1.IsOpen == false)
            {
                try
                {
                    //программу можно доработать информация из podrugomu.com/node/987

                    MessageBox.Show("Вы точно налили дезинфектор", "WARNING", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    Port1.PortName = label2.Text;
                    Port1.Open();
                    Port1.Write("Clean");
                   
                    MessageBox.Show("Процес чистки оборудования запущен", "Инфорамационное сообщение");
                 
                    button1.Enabled = false;
                    button2.Enabled = false;
                    button3.Enabled = false;
                    

                    if (Port1.ReadByte() == 1000)
                    {
                        button1.Enabled = true;
                        button2.Enabled = true;
                        button3.Enabled = true;
                       

                        Port1.Close();
                        MessageBox.Show("Конец Цикла чистки оборудования");
                    }
                }
                catch
                {
                    MessageBox.Show("Неверно выбран порт устройства. Процесс чистки не может быть запущен", "Warninig");
                }
            }
        }

        private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
        {

        }

        private void cOM1ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SerialPort Port1 = new SerialPort("COM1", 9600);
          //  MessageBox.Show("Выбран порт COM1");
            label1.Visible = true;
            label2.Text = "COM1";

        }

        private void label2_Click(object sender, EventArgs e)
        {

        }

        private void cOM2ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SerialPort Port1 = new SerialPort("COM2", 9600);
            //  MessageBox.Show("Выбран порт COM2");
            label1.Visible = true;
            label2.Text = "COM2";
        }

        private void cOM3ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SerialPort Port1 = new SerialPort("COM3", 9600);
            //  MessageBox.Show("Выбран порт COM3");
            label1.Visible = true;
            label2.Text = "COM3";
        }

        private void cOM4ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SerialPort Port1 = new SerialPort("COM4", 9600);
            //  MessageBox.Show("Выбран порт COM4");
            label1.Visible = true;
            label2.Text = "COM4";

        }

        private void cOM5ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SerialPort Port1 = new SerialPort("COM5", 9600);
            //  MessageBox.Show("Выбран порт COM5");
            label1.Visible = true;
            label2.Text = "COM5";
        }

        private void cOM6ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SerialPort Port1 = new SerialPort("COM6", 9600);
            //  MessageBox.Show("Выбран порт COM6");
            label1.Visible = true;
            label2.Text = "COM6";
        }

        private void cOM7ToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SerialPort Port1 = new SerialPort("COM7", 9600);
            //  MessageBox.Show("Выбран порт COM7");
            label1.Visible = true;
            label2.Text = "COM5";
        }

        private void программаВаркиToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }
        // Варка пива
        private void button2_Click(object sender, EventArgs e)
        {
           // if (Port1.IsOpen == false)
            //{
               // try
                //{
                    //программу можно доработать информация из podrugomu.com/node/987
                    //Port1.PortName = label2.Text;
                    //Port1.Open();
                    switch (label3.Text)
                    {
                        case "Выбрано инфузионное затирание":
                            MessageBox.Show("Процесс инфузионного затирания Запущен", "Инфорамационное сообщение");
                            Port1.WriteLine("i");
                            timer1.Start();
                            break;
             
                        case "Выбрана варка Cooper":
                            MessageBox.Show("Процесс затирания Cooper запущен", "Инфорамационное сообщение");
                            Port1.WriteLine("v");
                            timer3.Start();
                            break;

                    }



                    button1.Enabled = false;
                    button2.Enabled = false;
                    button3.Enabled = false;
                   // button5.Enabled = false;


                   // richTextBox1.Text = Port1.ReadLine()+"\n";
                   
                    
                    /*        if (Port1.ReadLine() == "e\r") {

                                button1.Enabled = true;
                                button2.Enabled = true;
                                button3.Enabled = true;
                                button5.Enabled = true; */
                                //Port1.Close();
                               // MessageBox.Show("Конец Цикла варки пива");
                               
                       // }
                       // }

                        
                        
                    
            
               // catch
                //{
                   // MessageBox.Show("Неверно выбран порт устройства. Процесс варки не может быть запущен", "Warninig");
                //}
          //  }









        }

        private void button5_Click(object sender, EventArgs e)
        {
            Port1.Write("p");
            button1.Enabled = false;
            button2.Enabled = false;
            button3.Enabled = false;
            timer2.Start();
            richTextBox1.Text = richTextBox1.Text + "\n" + "Запуск подогрева воды в заторном чане до 37 градусов" + " " + DateTime.Now.ToString("HH:mm");


           /* Port1.Open();
            
                //Port1.Open();

                //richTextBox1.Text = richTextBox1.Text + "\n" + Port1.ReadLine();
             
            Port1.WriteLine("o");
                Port1.Close(); */
            }
       

        private void выходToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void button4_Click(object sender, EventArgs e)
        {
            
        }

        private void инфузионноеЗатираниеToolStripMenuItem_Click(object sender, EventArgs e)
        {
            label3.Text = "Выбрано инфузионное затирание";
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            textBox1.Text = Convert.ToString(Convert.ToInt32(s / 60));
            textBox2.Text = Convert.ToString(Convert.ToInt32((s)));
            s++;
            label5.Text = Port1.ReadLine();

               if (s==4200){  //70 минут это 4200 секунд
                   timer1.Stop();
                   //timer2.Start();
                   //Port1.Open();
                   Port1.WriteLine("s");
                  // Port1.Close();
                   MessageBox.Show("инфузионное затирание окончено необходимо отфильтровать сусло");
                   button1.Enabled = true;
                   button2.Enabled = true;
                   button3.Enabled = true;
                   button5.Enabled = true;
               textBox1.Text = "";
               textBox2.Text = "";

               }
              
                   
                   //Port1.Close();
               
               

        }

        private void label4_Click(object sender, EventArgs e)
        {
        
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            label5.Text = Port1.ReadLine();

            if (Port1.ReadLine() == "s\r")
            {
                timer2.Stop();
                MessageBox.Show("Вода имеет температуру 37 градусов. Можно засыпать солод и включать режим затирания");
                richTextBox1.Text = richTextBox1.Text + "\n" + "Окончание процесса подготовки заторного чана. Т=37 градусов" + " " + DateTime.Now.ToString("HH:mm");
                button2.Enabled = true;
            }


        }

        private void аварийноеОтключениеToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Port1.WriteLine("a");
            MessageBox.Show("Пользователь аварийно отключил всю систему");
        }

        private void заторногоКотлаToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Port1.WriteLine("k");
            MessageBox.Show("Включен режим кипячения заторного чана. Ждите 60 минут");
            richTextBox1.Text = richTextBox1.Text + "\n" + "Включен режим кипячения заторного чана. Ждите 60 минут" + " " + DateTime.Now.ToString("HH:mm");
 
        }

        private void timer3_Tick(object sender, EventArgs e)
        {
            string s1 = "";
            Port1.ReadLine();
            if (s1.Substring(0, 2) == "t1")
                label5.Text = s1.Substring(4, 5);
            if (s1.Substring(0, 2) == "t2")
                label9.Text = s1.Substring(4, 5);

            s++;
            if (s == 900)
            {
                Port1.WriteLine("m");
                MessageBox.Show("Этап затирания при 62 градусах");
                richTextBox1.Text = richTextBox1.Text + "\n" + "Этап затирания при 62 градусах" + " " + DateTime.Now.ToString("HH:mm");
                
            }
            if(s== 2250)
            {
                Port1.WriteLine("n");
                MessageBox.Show("Этап затирания при 78 градусах");
                richTextBox1.Text = richTextBox1.Text + "\n" + "Этап затирания при 78 градусах" + " " + DateTime.Now.ToString("HH:mm");

                
            }

            if (s ==2700)
            {
                Port1.WriteLine("b");
                MessageBox.Show("Этап затирания закончен, можно сливать сусло");
                richTextBox1.Text = richTextBox1.Text + "\n" + "Этап затирания окончен. Можно сливать сусло" + " " + DateTime.Now.ToString("HH:mm");

                
            }

        }

        private void обычнаяToolStripMenuItem_Click(object sender, EventArgs e)
        {
            label3.Text = "Выбрана варка Cooper";
        }

        

    }
}




Программа общается с arduino через com порт. Единственное, никак не могу победить, чтобы при опрашивании com порта программа не подвисала на момент выполнения подпрограммы. После окончания подпрограммы программа отвисает, но пока это не критично и даже некий плюс. Защита от дурака — чтобы не нажать ничего во время определенной процедуры.

Итого, программа умеет


— готовить оборудование к варке;
— варить;
— писать простенький лог работы (очень полезно при дальнейшем анализе варки);
— выбирать порт для подключение к управляющему контроллеру(arduino UNO);
кипятить варочник и заторный чаны.

Когда придут помпы и солинойдные клапаны, буду автоматизировать дальше. А пока каждое воскресение буду добавлять по одной программе варки. В сумме будет 5 программ. Так же ждет своей очереди реализация ручного режима.
Как говорится,

to be continued...



UPD:


Вот несколько фотографий пивоварни

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

Медная трубка внутри — это для фильтрования сусла. Можно сделать красивее, но лучше использовать фальшдно. К сожалению китайские собрать не торопятся высылать его.

Пока рано говорить о результате, вот что получилось


Общий вид чана выглядит вот так.


Использовал обычные коннекторы
для подключения датчиков температуры, подключающиеся к двойной розетке. Удобнее мыть оборудование. Отключил, понес в ванную. Помыл, подключил и все работает.

Все пенилось по графику, лишнего не вытекало. А самых ближайших планах — слить на дображивание партию пива, сварить еще одну. Рецепт, в принципе можно посмотреть в коде в подпрограмме varka() у arduino.

Наконец-то пришли соленойдные клапаны. Процесс автоматизации продолжается.
Теги:
Хабы:
+3
Комментарии25

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн