Skocz do zawartości

Komora termiczna - kontrola procesu fermentacji


Pomocna odpowiedź

No 🙂

Przy okazji dorobiłem obsługę przekaźników do sterowania wentylatorami aby załączały mi się razem z ogniwami i wyłączały z opóźnieniem. Muszę zastanowić się czy mam taka funkcjonalność już jaką chciałem i brać się za budowę komory. No i pozbyć się zmiennych globalnych które są używane w tylko jednej funkcji, a zamiast tego użyć argumentów funkcji, ale to już zależy od tego czy mi się będzie chciało 🙂

Link do komentarza
Share on other sites

W końcu znalazłem trochę czasu na budowę komory. Styropian 10cm grubości, wartość oporu 3,00 m2K/W, współczynnik przewodzenia ciepła 0,033 W/mK więc mam nadzieję że wystarczy aby układ chłodzenia nie działał cały czas na pełnych obrotach, szczególnie że jego głównym zadaniem ma być utrzymywanie temperatury, a nie chłodzenie. Od środka i na zewnątrz oklejony ekranem za grzejnikowym (jako dodatkowa izolacja, tjuning optyczny i zabezpieczanie przed kruszeniem styropianu ). Przygotowałem dwie wersje pokrywy, pełną i z miejscem na układ chłodzenia (tą jeszcze kończę i zastanawiam się nad sposobem mocowania dosyć ciężkiego układu chłodzenia w styropianie). Wszystkie szczeliny wypełnione dodatkowo masą izolacyjną aby zminimalizować ilość mostków termicznych. Ekran wewnątrz klejony na zakładkę, z zewnątrz komora owinięta na około.

W tym tygodniu przeprowadzę testy jak sobie radzi z utrzymaniem temperatury wewnątrz na pełnej pokrywie. Mam nadzieję że w ciągu miesiąca uda już mi się odpalić całość z układem chłodzenia.

 

Komora

  • Lubię! 1
Link do komentarza
Share on other sites

Wczoraj skończyłem wszystko składać.

1.thumb.jpg.2cce53b3a1a376981c8e754c97fa44fb.jpg2.thumb.jpg.84bc87faa3369552a670822554e4c69f.jpg

Wygląda że będzie działać 🙂 . Jak odpaliłem podpinając na krótko i wykorzystując 1/3 mocy temperatura spadła o ok. 10°C w ciągu 15min. Nie testowałem jeszcze szczelności na tej pokrywie, przy pełnej wyrównanie temperatury wewnątrz z temp. otoczenia zajęła 5 dni kiedy w środku znajdowały się 2 butelki 1,5l z wodą o temperaturze początkowej 10°C.

Muszę jeszcze dodatkowo zaizolować duży radiator od góry, aby zmniejszyć wpływ otoczenia i jak wszystko dobrze wyschnie w pt pewnie będę sprawdzał jaką temperaturę minimalną uda się wewnątrz osiągnąć wykorzystując tym razem ok 1/2 mocy ogniw.

  • Lubię! 1
Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

(edytowany)

Skończyłem budować już komorę. Obecnie jestem w trakcie testów wydajności, zabezpieczeń, itp. Zmierzyłem pobór prądu przy maksymalnym obciążeniu i wyszło ~200W.

Całość prezentuje się następująco:

20191025_083158.thumb.jpg.74dd21a9869eda67aab024cddd76107d.jpg

Pokrywa z układem chłodzenia oraz sterownikiem:

20191025_083207.thumb.jpg.7cf5d2f176dab297bd0c1c1274314eb5.jpg

Sam sterownik:

20191025_083220.thumb.jpg.6b61b24799eead65b4d10a21296d115a.jpg

Kod programu, main.h:

#include <OneWire.h>
#include <DallasTemperature.h>
#include <EEPROM.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoJson.h>
#include <Timers.h>
#include "Virtuino_ESP_WifiServer.h"

void WriteEEPROM();
void ReadEEPROM();
void SendingValues();
void PeltierControl();
void CalcDiffTemp();
void ReadTemp();
void FanControlON();
void FanTimeControlOFF();
void ControlSecurityStatus();

String PrepareHtmlPage();

#define ONE_WIRE_BUS 15

#define EEPROM_SIZE 4

#define CoolingPin     33
#define WarmingPin     32
#define SignalPWM   22
#define EnablePin   25

#define FanHot  26
#define FanCold 27

#define VirtuinoControlTempDisplay 25

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

DXviXXAddrXss TXmpSXnsorXXrmXntXr  = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xX,  0xX, 0xXX };
DXviXXAddrXss TXmpSXnsorXhamXXr	   = { 0xXX, 0xXX, 0xX,  0xXX, 0xXX, 0xX,  0xX, 0xXX };
DXviXXAddrXss TXmpSXnsorRadiator   = { 0xXX, 0xXX, 0xXX, 0xX,  0xXX, 0xXX, 0xX, 0xXX };
DXviXXAddrXss TXmpSXnsorXontrollXr = { 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xX, 0xXX };

char APssid[] = "SSID";
char APpass[] = "PASSWORD";

WiFiServer VirtuinoServer(80);
WiFiServer iSpindelServer(81);

float TempiSpindel;
float TempFermenter;
float TempChamber;
float TempController;
float TempRadiator;

float Gravity;
float Battery;

float ImportanceTempiSpindel = 0.1;
float ImportanceTempChamber = 0.1;

float AverageTemp;
float SetTemp;
float DiffTemp;

float SavedTemp;

const int freq = 500;
const int ChanelPWM = 0;
const int resolution = 8;
int PWMFilling = 0;

byte iSpindelReading = 0;
byte SecurityStatus = 0;
byte PeltierStatus = 0;

unsigned long FanTime;
unsigned long FanHotTime = 25000;
unsigned long FanColdTime = 60000;

unsigned long ReadTime;
unsigned long ReadTempTime = 2000;

  float SecurityHighTempRadiator = 50.0;
  float SecurityLowTempRadiator = 35.0;
  float SecurityHighTempController = 50.0;
  float SecurityLowTempController = 35.0;
  byte SecurityControler = 0;
  byte SecurityRadiator = 0;

Oraz main.cpp:

#include "main.h"

Virtuino_ESP_WifiServer virtuino(&VirtuinoServer);

void setup() {
  virtuino.DEBUG = false;
  sensors.begin();
  EEPROM.begin(EEPROM_SIZE);
  
  pinMode(CoolingPin, OUTPUT);
  pinMode(WarmingPin, OUTPUT);
  pinMode(FanHot, OUTPUT);
  pinMode(FanCold, OUTPUT);
  pinMode(EnablePin, OUTPUT);

  ledcSetup(ChanelPWM, freq, resolution);
  ledcAttachPin(SignalPWM, ChanelPWM);

  ReadEEPROM();

  digitalWrite(CoolingPin, LOW);
  digitalWrite(WarmingPin, LOW);
  digitalWrite(FanHot, LOW);
  digitalWrite(FanCold, LOW);
  ledcWrite(ChanelPWM, 0);

  WiFi.mode(WIFI_AP);
  WiFi.softAP(APssid, APpass);

  virtuino.password = "1234";

  VirtuinoServer.begin();
  iSpindelServer.begin();
  delay(1000);
  delay(1000);  
}

void loop(void) {
  ControlSecurityStatus();

  WiFiClient iSpindelClient = iSpindelServer.available();
  virtuino.run();

  SetTemp = virtuino.vMemoryRead(0);

  if (SetTemp!=SavedTemp) {
    WriteEEPROM();
  }

  SavedTemp = SetTemp;

  if (iSpindelClient) {
      while (iSpindelClient.connected()) {      
      if (iSpindelClient.available()) {
        String line = iSpindelClient.readStringUntil('\r');
        const size_t capacity = JSON_OBJECT_SIZE(9) + 90;
        const char* json = line.c_str();
        DynamicJsonDocument doc(capacity);
        deserializeJson(doc, json);
        if (doc["ID"] == 1234567) {
          TempiSpindel = doc["temperature"];
          Gravity = doc["gravity"];
          Battery = doc["battery"];
          iSpindelReading = 1;
        }
      }
    };
    delay(1);
    iSpindelClient.stop();
  }

  if ((millis() - ReadTime) >= ReadTempTime) {
    ReadTemp();
    ReadTime = millis();
  }
  
  CalcDiffTemp();

  if (PeltierStatus != 0) {
    PeltierControl();
  }

  if (virtuino.lastCommunicationTime != 0) {
    SendingValues();
    virtuino.lastCommunicationTime = 0;
  }

  FanTimeControlOFF();
  yield();
}

void ReadTemp() {
  sensors.requestTemperatures();  
  TempFermenter = sensors.getTempC(TempSensorFermenter);
  TempChamber = sensors.getTempC(TempSensorChamber);
  TempController = sensors.getTempC(TempSensorController);
  TempRadiator = sensors.getTempC(TempSensorRadiator);
}

void CalcDiffTemp() {
  if (iSpindelReading != 0) {
    AverageTemp = TempiSpindel * ImportanceTempiSpindel + TempChamber * ImportanceTempChamber + TempFermenter * (1.0F - ImportanceTempiSpindel - ImportanceTempChamber);
  } else {
    AverageTemp = TempChamber * ImportanceTempChamber + TempFermenter * (1.0F - ImportanceTempChamber);
  }
  DiffTemp = round((SetTemp - AverageTemp)*10)/10;
  PWMFilling = fabs(DiffTemp)*510;

  if (DiffTemp <= -0.3F) {
    digitalWrite(CoolingPin, HIGH);
    digitalWrite(WarmingPin, LOW);
    PeltierStatus = 1;
  } else if (DiffTemp >= 0.3F) {
    digitalWrite(CoolingPin, LOW);
    digitalWrite(WarmingPin, HIGH);
    PeltierStatus = 1;
  } else if (abs(DiffTemp) <= 0.1F) {
    digitalWrite(CoolingPin, LOW);
    digitalWrite(WarmingPin, LOW);
    PeltierStatus = 0;
  }  
}

void SendingValues() {
  virtuino.vMemoryWrite(1, TempiSpindel);
  virtuino.vMemoryWrite(2, TempFermenter);
  virtuino.vMemoryWrite(3, TempChamber);
  virtuino.vMemoryWrite(4, AverageTemp);
  virtuino.vMemoryWrite(5, Gravity);
  virtuino.vMemoryWrite(6, Battery);
  virtuino.vMemoryWrite(7, TempController);
  virtuino.vMemoryWrite(8, TempRadiator);

  if (SecurityStatus == 0 && digitalRead(CoolingPin) != 0) {
    virtuino.vMemoryWrite(9, -PWMFilling);
  } else if (SecurityStatus == 0 && digitalRead(WarmingPin) != 0) {
    virtuino.vMemoryWrite(9, PWMFilling);
  } else {
    virtuino.vMemoryWrite(9, 0);
  }

  virtuino.vMemoryWrite(10, digitalRead(FanHot));
  virtuino.vMemoryWrite(11, digitalRead(FanCold));
}

void PeltierControl() {
  if (SecurityStatus == 0) {    
    if (abs(DiffTemp) < 0.50F) {
      digitalWrite(EnablePin, HIGH);      
      FanControlON();
      ledcWrite(ChanelPWM, PWMFilling);
    } else {
      digitalWrite(EnablePin, HIGH);
      ledcWrite(ChanelPWM, 255);
      FanControlON();
    }
  }
}

void WriteEEPROM() {
  EEPROM.put(0, SetTemp);
  delay(100);
  EEPROM.commit();
  delay(100);
}

void ReadEEPROM() {
  SetTemp = EEPROM.get(0, SetTemp);
  delay(100);
  SavedTemp = SetTemp;
  virtuino.vMemoryWrite(0, SetTemp);
  delay(100);
}

void FanControlON() {
  digitalWrite(FanHot, HIGH);
  digitalWrite(FanCold, HIGH);
  FanTime = millis();
}

void FanTimeControlOFF() {
  if (SecurityStatus == 0) {
    if ((millis() - FanTime) >= FanHotTime) {
      digitalWrite(FanHot, LOW);
      digitalWrite(EnablePin, LOW);
    } 
    if ((millis() - FanTime) >= FanColdTime) {
      digitalWrite(FanCold, LOW);
      digitalWrite(EnablePin, LOW);
    }
  }
}

String PrepareHtmlPage() {
  String htmlPage =
     String("HTTP/1.1 200 OK\r\n") +
            "Content-Type: text/html\r\n" +
            "Connection: close\r\n" +  // the connection will be closed after completion of the response
            "Refresh: 5\r\n" +  // refresh the page automatically every 5 sec
            "\r\n" +
            "<!DOCTYPE HTML>" +
            "<html>" +
            "Analog input:  " + String(analogRead(A0)) +
            "</html>" +
            "\r\n";
  return htmlPage;
}

void ControlSecurityStatus() {
  if (TempRadiator >= SecurityHighTempRadiator && SecurityStatus == 0) {
    SecurityStatus = 1;
    SecurityRadiator = 1;
    FanControlON();
    digitalWrite(EnablePin, LOW);
  } else if (TempController >= SecurityHighTempController && SecurityStatus == 0) {
    SecurityStatus = 1;
    SecurityControler = 1;
    digitalWrite(FanCold, LOW);
    digitalWrite(FanHot, LOW);
    digitalWrite(EnablePin, LOW);
  } else if (SecurityStatus == 1 && TempRadiator <= SecurityLowTempRadiator && SecurityControler == 0) {
    SecurityStatus = 0;
    SecurityRadiator = 0;
  } else if (SecurityStatus == 1 && TempController <= SecurityLowTempController && SecurityRadiator == 0) {
    SecurityStatus = 0;
    SecurityControler = 0;
  }
}

Obecnie jeszcze tak jak pisałem sprawdzam poprawność działania, testuje wartości opóźnień (np. odczyt temperatury co 2s jest zbyt częsty i bezsensowny, ale do testów przydatny). Miałem trochę zabawy z parsowaniem JSONa przesyłanego od iSpindla ale działa już tak jak powinno 🙂 No i teraz komunikacja następuje co 10s, ostatecznie pewnie będzie co 30min.

Całością steruję za pomocą aplikacji stworzonej w Virtuino, myślałem o BLYNK, ale doszedłem do wniosku że sterowanie przez internet jest zbędne.

Screenshot_20191025-085302_Virtuino.thumb.jpg.e7305403ed6589ec9191d11e18428320.jpg

Na górze aplikacji wyświetlam uśrednioną temperaturę z trzech termometrów (w iSpindlu, przyczepiony do boku pojemnika, bezpośrednio z komory) oraz zawartość cukru w płynie (na razie iSpindel nie jest skalibrowany poprawnie). Tym dużym pokrętłem reguluję temperaturę która chcę mieć. Poniżej wyświetlam moc chłodzenia/grzania (mocno zmniejszyłem histerezę względem założeń), oraz wyświetlam temperaturę z trzech czujników. Dwie pompy sygnalizują pracę wentylatorów na stronie ciepłej i zimnej. Na samym dole mam informacje o napięciu w iSpindlu, odczyt temperatury z czujnika znajdującego się obok radiatorów doczepionych do mostków, oraz czujnika przyczepionego do radiatora od ogniwa Peltiera.

Chcę jeszcze dodać wyświetlacz LCD i możliwość sterowania za pomocą fizycznych przycisków, ale na razie nie mam pomysłu jak to miało by działać.

Edytowano przez szczawiosław
  • Lubię! 1
Link do komentarza
Share on other sites

@szczawiosław wygląda bardzo fajnie, całkiem rozbudowany projekt z tego wyszedł!

4 godziny temu, szczawiosław napisał:

Całością steruję za pomocą aplikacji stworzonej w Virtuino, myślałem o BLYNK, ale doszedłem do wniosku że sterowanie przez internet jest zbędne.

I jak oceniasz prace z Virtuino? Dobrze się sprawdziło?

Link do komentarza
Share on other sites

Do mojego zastosowania całkiem spoko się sprawdziło. BLYNK jest łatwiejszy w obsłudze, dodatkowo jeżeli postawimy własny serwer mamy praktycznie nieograniczoną wielkość aplikacji, w Viruino niestety ograniczenia nie ominiemy za wyjątkiem wersji płatnej. Sama aplikacja jest dosyć rozbudowana jednak wydaje się trochę toporna. Brakowało mi jakiegoś wigetu który umożliwia zmianę wartości przyciskami + i - (stąd pokrętło, które jest strasznie czułe). Aplikacja na pewno dobra jeżeli chcemy mieć wpływ na każdy możliwy aspekt wyglądu aplikacji. Mamy możliwość obrabiania danych w aplikacji, nie musimy tego implementować w kodzie programu. Sama praca z aplikacją przez mnogość ustawień i mała przejrzystość bywa mocno upierdliwa. Dodatkowo jeżeli chcemy mieć wykres wartości z jakiegoś okresu w przypadku braku serwera musimy przechowywać w pamięci mikrokontrolera.

  • Lubię! 1
Link do komentarza
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.