Fermentor építés – (2. rész) – szén-dioxid detektálás

Bevezetés 

A fermentáció olyan technológiai folyamat, amelynek során a jelen levő élő mikroorganizmusok szaporodásának, életfolyamataik és enzimjeik hatására bonyolult biokémiai változások mennek végbe az alapanyagokban. E folyamatot jellemzően baktériumok és/vagy gombák – kivételes esetekben algák, növényi eukarióta sejtek esetleg emlőssejtek – használatával végzik, és szénhidrátok lebontása során primer, illetve szekunder metabolitokat (anyagcseretermékeket) állítanak elő, vagy biokonverzióval értékes anyagokat alakítanak át. Tipikus fermentációs termékek az aminosavak, enzimek, vitaminok, antibiotikumok(pl. penicillinek, sztreptomicinek, tetraciklinek, stb.), konvertált szteroidok (biotranszformáció) és rekombináns fehérjék (pl. r-inzulin, r-kalcitonin, stb.). Természetesen termékek lehetnek önmagukban is a baktériumok és gombák (biomassza), vagy általuk előállított fehérjék és olajok (pl. probiotikumok, egysejt-fehérjék és olajok).

Az aerob fermentáció általános egyenlete, ha egy extracelluláris anyag keletkezik:

CHmOn + aO2 + bNH3 → cCHαOβNδ + dCHxOyNz + e H2O + fCO2

CHmOn = Kiindulási anyag, szubsztrát, amit a mikroorganizmusok a sejtfelépítésükhöz használnak.

O2 = Aerob fermentációnál az oxigén jelenléte elengedhetetlen, ezt folyamatosan pumpáljuk a biorektorba.

NH3 = Nitrogénforrás.

CHαOβNδ = Biomassza.

CHxOyNz = Keletkezett termék.

H2O = Víz.

CO2 = Keletkezett szén-dioxid, aminek majd mérjük a koncentrációját.

 

A szenzor

Hosszas keresgélés után végül a SprintIR szenzorra esett a választás, az ultragyors mintavételezése (20érték/másodperc), a széles mérési tartománya (0-100%, én egy 0-5% választottam → jobb felbontás), a pontossága és persze a kedvező ára miatt,  130€ + 20€ az adapter. Egyetlen negatívuma, hogy I2C helyett csak UART adattovábbítást tud (vagyis csak nekünk, mert  a pH méréshez I2C-t használunk).

co2sensor_pic

Adatlapja itt érhető el: https://www.gassensing.co.uk/media/1091/sprintir_-datasheet_gss.pdf

 

Szenzor teszt

Megfelelő törzsek kiválasztására, előszűrésére ezt a típusú fermentort (DASbox®) használjuk. 100-150ml-es fermentációkat tudunk benne kivitelezni, lényegében ugyanazt tudja mint egy 1,5-3-5-7 literes, pH, pO2, hűtés- fűtés, keverés, automata feed start, stb. E.coli fermentációknál általában Fed-batch, vagyis rátáplálásos fermentációt alkalmazunk, ez azt jelenti, hogy a szükséges tápanyagokat, ionokat nem egyszerre, hanem folyamatosan adagoljuk a fermentáció közben, ami akár egy hét is lehet.

A módszer, hogy megvizsgáljuk a tápanyag adagolás hatékonyságát, elkerülve a tápanyag felhalmozódását, egy kis időre kikapcsoljuk a szénforrás adagolást, majd megfigyeljük a pO2-t, vagy a távozó gáz CO2 koncentráció változását. A mi esetünkben most a CO2 szintet vizsgáljuk.

co2ppm

Egy stacionárius fázisban lévő fermentáció CO2 kibocsátása közel egyenletes, ebben az esetben kb. 16500ppm, a bepumpált levegő CO2 koncentrációja kb. 380-400ppm, az áramlási sebesség pedig 0,1L/min. A tenyészet OD600 értéke 180, ami elég nagy sejtsűrűséget jelent, hozzávetőlegesen 1011db sejt/mL. Tehát, ha megszüntetjük a szénforrás utánpótlást, és a médium nem tartalmaz feleslegben belőle, akkor a sejtek elkezdenek éhezni, nincs szénforrás lebontás, a kibocsátott CO2 koncentrációja csökken, ezt láthatjuk a grafikonon is, nem az elején (itt véletlenül lecsatlakoztattam a csövet a szenzorról), hanem kb. a 25. percnél.

Adatfeldolgozás

A cél az, hogy raspberry-vel olvassuk a szenzort, és a mért értékeket böngészőben tudjuk megjeleníteni, de ez egy kissé bonyolult. Jelenleg Arduino-val tudom csak használni, ez USB-n keresztül csatlakozik a számítógéphez, így az adatokat soros porton keresztül egyszerűen tudom olvasni. A szenzor pin kiosztásával volt egy kis bajom, így oszcilloszkóppal bemértem, hogy működik-e, jöttek a nullák és az egyesek, úgyhogy jó volt.

co2_sensor_oscilloscope2

A kód

/*
  COZIR Sample code
  Written by: Jason Berger ( for Co2meter.com)
   
  This sketch connects will connect to a COZIR gss or SprintIR sensor
  and report readings back to the host computer over usb. The value is  
  stored in a global variable 'co2' and can be used for any number of applications.
   
  pin connections:
   
  Arduino________COZIR Sensor
   GND ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 1 (gnd)
   3.3v‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 3 (Vcc)  
    10 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 5 (Rx)
    11 ‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ 7 (Tx)
*/
#include <SoftwareSerial.h>
SoftwareSerial mySerial(10, 11); // RX, TX
String val= "";  //holds the string of the value
double co2 =0;  // holds the actual value
double multiplier = 10; //each range of sensor has a different value.
                        // up to 2% =1
                        // up to 65% = 10
                        //up to 100% = 100;
uint8_t buffer[25];
uint8_t ind =0;
void setup()   
{
  Serial.begin(9600);                            //Start Serial connection with host
  Serial.println("Co2Meter.com COZIR Sample");
mySerial.begin(9600);                          //Start Serial connection with Sensor
  
}
void loop()
{
  //COZIR sensors ship from the factory in streaming mode
  //So we read incoming bytes into a buffer until we get '0x0A' which is the ASCII value for new‐line
  while(buffer[ind‐1] != 0x0A)
  {
    if(mySerial.available())
    {
      buffer[ind] = mySerial.read();
      ind++;
    }
  }
   
  report();  //Once we get the '0x0A' we will report what is in the buffer
}
void report()
{
  //Cycle through the buffer and send out each byte including the final linefeed
   
  /*
    each packet in the stream looks like "Z 00400 z 00360"
    'Z' lets us know its a co2 reading. the first number is the filtered value
    and the number after the 'z' is the raw value.
    We are really only interested in the filtered value
   
  */
   
  for(int i=0; i < ind+1; i++)
  {
    if(buffer[i] == 'z') //once we hit the 'z' we can stop
      break;
       
    if((buffer[i] != 0x5A)&&(buffer[i] != 0x20)) //ignore 'Z' and white space
    {
      val += buffer[i]‐48;  //because we break at 'z' the only bytes getting added are the numbers
                            // we subtract 48 to get to the actual numerical value
                            // example the character '9' has an ASCII value of 57. [57‐48=9]             
   
  }
  }
   
  co2 = (multiplier * val.toInt());  //now we multiply the value by a factor specific ot the sensor. see the
COZIR software guide
  Serial.print( "Co2 = ");
  Serial.print(co2);
  Serial.println(" ppm");
    ind=0; //Reset the buffer index to overwrite the previous packet
    val=""; //Reset the value string
}

A fenti kódot egyszerűen feltöltjük az Arduino modulra, csatlakoztatjuk a szenzort, megnyitjuk a megfelelő COM portot és már jönnek is az adatok, nagyon gyorsan, kb. 16 érték/mp volt a legutóbbi mérésnél.

#include <SoftwareSerial.h> Könytár letöltése ⇒ SoftwareSerial

Felhasznált irodalom

http://www.tankonyvtar.hu/hu/tartalom/tkt/fermentacios/ch01.html

http://www.co2meters.com/Documentation/AppNotes/AN128-%20Cozir_Arduino.pdf

Share This:

 
Loading Facebook Comments ...

COMMENTS

  • acet <cite class="fn">acet</cite>

    Ezt kellene egy kicsit kipofozni, néha átküldi az értéket, néha csak nullát küld, ha meg semmit akkor meg a többi eszközről jövő adat sem látszik.

    /*
      COZIR Sample code
      Written by: Jason Berger ( for Co2meter.com)
       
      This sketch connects will connect to a COZIR gss or SprintIR sensor
      and report readings back to the host computer over usb. The value is  
      stored in a global variable 'co2' and can be used for any number of applications.
       
      pin connections:
       
      Arduino________COZIR Sensor
       GND ------------------ 1 (gnd)
       3.3v------------------- 3 (Vcc)  
        10 -------------------- 5 (Rx)
        11 -------------------- 7 (Tx)
    */
    #include <SoftwareSerial.h>
    #include <Wire.h>
    SoftwareSerial mySerial(10, 11); // RX, TX
    String val= "";  //holds the string of the value
    double co2 =0;  // holds the actual value
    double multiplier = 10; //each range of sensor has a different value.
                            // up to 2% =1
                            // up to 65% = 10
                            //up to 100% = 100;
    uint8_t buffer[25];
    uint8_t ind =0;
    void setup()   
    {
      Serial.begin(9600);                            //Start Serial connection with host
      Serial.println("Co2Meter.com COZIR Sample");   //Start Serial connection with Sensor
      mySerial.begin(9600);      
      Wire.begin(8);
      Wire.onRequest(requestEvent);                    
      
    }
    void requestEvent()
    {
    
    int32_t bigNum = co2;
    char str[255];
    sprintf(str, "%d\0", bigNum);
    Wire.write(str);
    
    }
    void loop()
    
    {
      
      //COZIR sensors ship from the factory in streaming mode
      //So we read incoming bytes into a buffer until we get '0x0A' which is the ASCII value for new-line
      while(buffer[ind-1] != 0x0A)
      {
        if(mySerial.available())
        {
      
          buffer[ind] = mySerial.read();
          ind++;
        }
    
      }
     
      report();  //Once we get the '0x0A' we will report what is in the buffer
     
    }
    
    void report()
    {
      //Cycle through the buffer and send out each byte including the final linefeed
       
      /*
        each packet in the stream looks like "Z 00400 z 00360"
        'Z' lets us know its a co2 reading. the first number is the filtered value
        and the number after the 'z' is the raw value.
        We are really only interested in the filtered value
       
      */
       
      for(int i=0; i < ind+1; i++)
      {
        if(buffer[i] == 'z') //once we hit the 'z' we can stop
          break;
           
        if((buffer[i] != 0x5A)&&(buffer[i] != 0x20)) //ignore 'Z' and white space
        {
          val += buffer[i]-48;  //because we break at 'z' the only bytes getting added are the numbers
                                // we subtract 48 to get to the actual numerical value
                                // example the character '9' has an ASCII value of 57. [57-48=9]             
       
      }
      }
    
       
      co2 = (multiplier * val.toInt());  //now we multiply the value by a factor specific ot the sensor. see the COZIR software guide
      
      Serial.println(co2);
    
    
    
      
        ind=0; //Reset the buffer index to overwrite the previous packet
        val=""; //Reset the value string
    
    
    }
    
  • acet <cite class="fn">acet</cite>

    Ezt a két sor kivettem

    Serial.begin(9600);
    Serial.println(“Co2Meter.com COZIR Sample”);

    Az a helyzet, hogy az olvasás néha sikerül, néha nem, csatoltam egy képet logikai analizátorral így néz ki:
    I2C CO2

    Amikor az olvasás sikeres:

    a kommunikáció hossza 185ms
    kiolvasott érték 49+52+57+48 (ASCII) ez 1490 lesz ami a CO2 ppm-ben mért koncentrációja
    az egyes értékek olvasása között 20mikrosec van.
    A kommunikáció lezárása 255+ACK…….255+NAK

    Amikor az olvasás sikertelen:

    a kommunikáció hossza 215ms
    kiolvasott érték 49+52 = 14
    az egyes értékek olvasása között 0,9ms van.
    A kommunikáció lezárása 177+NAK itt a két olvasott érték után lezárja

    A szenzorból jövő érték mindig felülírja co2 változót és talán amikor a a raspberry olvas és pont akkor változik az érték akkor összezavarodik?

  • acet <cite class="fn">acet</cite>

    A huszadik érték olvasása után pedig 0-át ad vissza mindig, vagy semmit, ha újraindítom az Arduino-t akkor kezdődik elölről.

  • Guttmann Krisztián <cite class="fn">Guttmann Krisztián</cite>

    így kéne megpróbálni:

    void loop()
    {
      delay(1000);
      Serial.begin(9600);
      mySerial.begin(9600);
      //COZIR sensors ship from the factory in streaming mode
      //So we read incoming bytes into a buffer until we get '0x0A' which is the ASCII value for new‐line
      while(buffer[ind‐1] != 0x0A)
      {
        ...
      }
      report();  //Once we get the '0x0A' we will report what is in the buffer
      Serial.end();
      mySerial.end();
    }
  • Guttmann Krisztián <cite class="fn">Guttmann Krisztián</cite>

    így néz ki most a fermi szerver oldali kódja:

    korábban 2 fileban volt a forráskod most összegyúrtam 1be, így szerintem átláthatóbb:

    importScripts("libcore-base.js");
    importScripts("class.js");
    importScripts("connection.js");
    importScripts("server.js");
    
    importScripts("ds1621.js");
    importScripts("jd0002.js");
    importScripts("ezo_ph.js");
    importScripts("co2.js");
    
    new (Server.extend({
    	init: function() {
    		this.base();
    
    		/* Initalize sensors           */
    		console.log("Initalizing sensors...");
    		this.sensor1 = new JD0002(3);
    		this.sensor2 = new CO2(5);
    		this.sensor3 = new CO2(9);
    		this.sensor4 = new EZO_PH(99);
    		this.sensor5 = new CO2(8);
    
    		try { this.sensor1.measure(); } catch(e) {  }
    		try { this.sensor2.measure(); } catch(e) {  }
    		try { this.sensor3.measure(); } catch(e) {  }
    		try { this.sensor4.measure(); } catch(e) {  }
    
    		/* Initialize database         */
    		console.log("Initalizing database...");
    		if (localStorage.getItem("measures") === null) {
    			this.database = [];
    			console.log("Creating database...");
    			localStorage.setItem("measures", JSON.stringify([]));
    		} else {
    			console.log("Reading database...");
    			this.database = JSON.parse(localStorage.getItem("measures"));
    		}
    
    		/* Initialize logging */
    		setInterval(this.ontimeout.bind(this), 1000*60);
    
    		/* Done. We are up and running */
    		console.log("Application server is up");
    		application = this;
    	},
    	ontimeout: function(event) {
    		var value1 = null;
    		var value2 = null;
    		var value3 = null;
    		var value4 = null;
    		var value5 = null;
    
    		try { value1 = this.sensor1.value; } catch(e) {  }
    		try { value2 = this.sensor2.value; } catch(e) {  }
    		try { value3 = this.sensor3.value; } catch(e) {  }
    		try { value4 = this.sensor4.value; } catch(e) {  }
    		try { value5 = this.sensor5.value; } catch(e) {  }
    
    		/* Save sensor values */
    		this.database.push({
    			dt: (new Date()).getTime(),
    			t1: value1,
    			t2: value2,
    			t3: value3,
    			ph: value4,
    			co2: value5
    		});
    		localStorage.setItem("measures", JSON.stringify(this.database));
    
    		try { this.sensor1.measure(); } catch(e) {  }
    		try { this.sensor2.measure(); } catch(e) {  }
    		try { this.sensor3.measure(); } catch(e) {  }
    		try { this.sensor4.measure(); } catch(e) {  }
    	},
    	onconnect: function(event) {
    		console.log("onconnect event");
    		return(true);
    	},
    	onopen: function(client) {
    		console.log("onopen event");
    	},
    	onclose: function(client) {
    		console.log("onclose event");
    	},
    	list: function(client, from, to) {
    		console.log("list method called");
    		var from = (new Date(from)).getTime();
    		var to = (new Date(to)).getTime();
    
    		client.rcall("onupdatedatatable", this.database.filter(function(e) {
    			return (e.dt>from && e.dt<to)
    		}) );
    	}
    }))();
    
    
    • acet <cite class="fn">acet</cite>

      ATmega-val nem tudtam tizedes értéket küldeni, úgyhogy az analóg bemenetén olvasott értéket küldi el, amit majd a szerver oldali kód alakít át hőmérsékletté, ez itt:

      value3 = Math.log(9870*((1023/value3)-1));
      value3 = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * value3 * value3 ))* value3 );
      value3 = value3 – 273.15;

      Korábban ez a kliens oldalon volt, akkor még plusz egy sor, az alábbi jön hozzá
      this.parameters_datatable.cells(3, 1).value = value3;

Leave a Comment