Ultraviolet Intensity Measurement Experiment: Unterschied zwischen den Versionen

Aus DL8RDS Wiki
Wechseln zu: Navigation, Suche
(Die Seite wurde neu angelegt: „== Project Scope == When my daughter (my first child) was still a baby, my father bought a UV sensor so he could measure the UV intensity. Babys are very sens…“)
 
 
(10 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 11: Zeile 11:
 
There are pairs of ML8511 sensors, one pair simply behind the SiO (Silicium Oxide / Quarz glass), and another pair behind a lowpass UVB filter so that these two can measure exclusively UV-A. The UVB intensity is then the difference between the tutal power in this spectral region minus the UVA.  
 
There are pairs of ML8511 sensors, one pair simply behind the SiO (Silicium Oxide / Quarz glass), and another pair behind a lowpass UVB filter so that these two can measure exclusively UV-A. The UVB intensity is then the difference between the tutal power in this spectral region minus the UVA.  
  
Here is a nice overview of subnurn effects:
+
Here is a nice overview of sunburn effects:
  
[[Image:2017-07-31-sunburn1.jpg|400px]]
+
[[Image:2017-07-31-sunburn1.png|600px]]
 +
 
 +
The sensor will display all measured values including a normalization on a web page. I am also interested in other environmental data (is the sensor doing fine?), so I decided to use a BME280 sensor also.
 +
 
 +
The ADS1115 has a programmable sensitivity feature, and the ML8511 runs on 3,3V. So I decided to use this 16 bit ADC on a Vmax basis of 4,096V, since the signal will not exceed 3,3V anyway. 3,28V is Vmax according to the sensor's spec and it will correlate with a brutal amount of 15mW/cm².
 +
 
 +
Here is the rough output scale of the sensor:
 +
 
 +
[[Image:2017-07-31-UV_intensity_ml8511.png|600px]]
  
 
== Component List ==
 
== Component List ==
Zeile 19: Zeile 27:
 
* Hammond 1550E aluminium diecast case: 50 €
 
* Hammond 1550E aluminium diecast case: 50 €
 
* 2sided base PCB: 2 €
 
* 2sided base PCB: 2 €
* Arduino Ethernet: 35
+
* Arduino Ethernet: 65
 +
: http://vampiresquidlabs.com/cms/shop/arduino-uno-ethernet/
 
* Programmer: 3 €
 
* Programmer: 3 €
 
* Watterott BME280: 14 €
 
* Watterott BME280: 14 €
Zeile 41: Zeile 50:
 
* TP-Link PoE injector: 25
 
* TP-Link PoE injector: 25
  
Total: 369
+
Total: ~400
 +
 
 +
== Code ==
 +
 
 +
<pre>
 +
#include <SPI.h>
 +
#include <Ethernet.h>
 +
#include <Wire.h>
 +
#include <Adafruit_ADS1015.h>
 +
#include "cactus_io_BME280_I2C.h"
 +
 
 +
Adafruit_ADS1115 ads;  /* Use this for the 16-bit version */
 +
 
 +
// Create the BME280 object
 +
BME280_I2C bme;              // I2C using default 0x77
 +
// or BME280_I2C bme(0x76);  // I2C using address 0x76
 +
 
 +
// Enter a MAC address and IP address for your controller below.
 +
// The IP address will be dependent on your local network:
 +
byte mac[] = {
 +
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
 +
};
 +
IPAddress ip(192, 168, 178, 177);
 +
 
 +
// Initialize the Ethernet server library
 +
// with the IP address and port you want to use
 +
// (port 80 is default for HTTP):
 +
EthernetServer server(80);
 +
 
 +
void setup() {
 +
  // Open serial communications and wait for port to open:
 +
  Serial.begin(9600);
 +
  while (!Serial) {
 +
    ; // wait for serial port to connect. Needed for native USB port only
 +
  }
 +
 
 +
 
 +
  // start the Ethernet connection and the server:
 +
  Ethernet.begin(mac, ip);
 +
  server.begin();
 +
  Serial.print("server is at ");
 +
  Serial.println(Ethernet.localIP());
 +
 
 +
  // The ADC input range (or gain) can be changed via the following
 +
  // functions, but be careful never to exceed VDD +0.3V max, or to
 +
  // exceed the upper and lower limits if you adjust the input range!
 +
  // Setting these values incorrectly may destroy your ADC!
 +
  //                                                                ADS1015  ADS1115
 +
  //                                                                -------  -------
 +
  // ads.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 3mV      0.1875mV (default)
 +
  ads.setGain(GAIN_ONE);          // 1x gain  +/- 4.096V  1 bit = 2mV      0.125mV
 +
  // ads.setGain(GAIN_TWO);        // 2x gain  +/- 2.048V  1 bit = 1mV      0.0625mV
 +
  // ads.setGain(GAIN_FOUR);      // 4x gain  +/- 1.024V  1 bit = 0.5mV    0.03125mV
 +
  // ads.setGain(GAIN_EIGHT);      // 8x gain  +/- 0.512V  1 bit = 0.25mV  0.015625mV
 +
  // ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV
 +
 
 +
  ads.begin();
 +
 
 +
  if (!bme.begin()) {
 +
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
 +
    while (1);
 +
  }
 +
 
 +
  bme.setTempCal(-1);
 +
}
 +
 
 +
 
 +
//The Arduino Map function but for floats
 +
//From: http://forum.arduino.cc/index.php?topic=3922.0
 +
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
 +
{
 +
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
 +
}
 +
 
 +
void loop() {
 +
  // listen for incoming clients
 +
  EthernetClient client = server.available();
 +
  if (client) {
 +
    Serial.println("new client");
 +
    // an http request ends with a blank line
 +
    boolean currentLineIsBlank = true;
 +
    while (client.connected()) {
 +
      if (client.available()) {
 +
        char c = client.read();
 +
        Serial.write(c);
 +
        // if you've gotten to the end of the line (received a newline
 +
        // character) and the line is blank, the http request has ended,
 +
        // so you can send a reply
 +
        if (c == '\n' && currentLineIsBlank) {
 +
          // send a standard http response header
 +
          client.println("HTTP/1.1 200 OK");
 +
          client.println("Content-Type: text/html");
 +
          client.println("Connection: close");  // the connection will be closed after completion of the response
 +
          client.println("Refresh: 5");  // refresh the page automatically every 5 sec
 +
          client.println();
 +
          client.println("<!DOCTYPE HTML>");
 +
          client.println("<html>");
 +
         
 +
          client.println("<table>");
 +
         
 +
          client.print("<tr><td>");
 +
          client.print("Light Intensity:");
 +
          client.print("</td><td>");
 +
          int sensorReading = analogRead(0);
 +
          Serial.print("Light Intensity: "); Serial.println(sensorReading);
 +
          client.print(sensorReading);
 +
          client.println("</td></tr>");
 +
 
 +
          int16_t adc0, adc1, adc2, adc3;
 +
          float adc0_1, adc1_1, adc2_1, adc3_1;
 +
          float uvIntensity_0, uvIntensity_1, uvIntensity_2, uvIntensity_3;
 +
         
 +
          client.print("<tr><td>");
 +
          client.print("UVB1 (mW/cm2):");
 +
          client.print("</td><td>");
 +
          adc0 = ads.readADC_SingleEnded(0);
 +
          adc0_1 = (float)adc0 / 8000;
 +
          uvIntensity_0 = mapfloat(adc0_1, 0.987, 2.8, 0.0, 15.0);
 +
          Serial.print("UVB1: "); Serial.println(uvIntensity_0);
 +
          client.print(uvIntensity_0);
 +
          client.println("</td></tr>");
 +
 
 +
          client.print("<tr><td>");
 +
          client.print("UVB2 (mW/cm2):");
 +
          client.print("</td><td>");
 +
          adc1 = ads.readADC_SingleEnded(1);
 +
          adc1_1 = (float)adc1 / 8000;
 +
          uvIntensity_1 = mapfloat(adc1_1, 0.999, 2.8, 0.0, 15.0);
 +
          Serial.print("UVB2: "); Serial.println(uvIntensity_1);
 +
          client.print(uvIntensity_1);
 +
          client.println("</td></tr>");
 +
 
 +
          client.print("<tr><td>");
 +
          client.print("UV1 (mW/cm2):");
 +
          client.print("</td><td>");
 +
          adc2 = ads.readADC_SingleEnded(2);
 +
          adc2_1 = (float)adc2 / 8000;
 +
          uvIntensity_2 = mapfloat(adc2_1, 0.989, 2.8, 0.0, 15.0);
 +
          Serial.print("UV1: "); Serial.println(uvIntensity_2);
 +
          client.print(uvIntensity_2);
 +
          client.println("</td></tr>");
 +
 
 +
          client.print("<tr><td>");
 +
          client.print("UV2 (mW/cm2):");
 +
          client.print("</td><td>");
 +
          adc3 = ads.readADC_SingleEnded(3);
 +
          adc3_1 = (float)adc3 / 8000;
 +
          uvIntensity_3 = mapfloat(adc3_1, 0.999, 2.8, 0.0, 15.0);
 +
          Serial.print("UV2: "); Serial.println(uvIntensity_3);
 +
          client.print(uvIntensity_3);
 +
          client.println("</td></tr>");
 +
 
 +
          bme.readSensor();
 +
         
 +
          client.print("<tr><td>");
 +
          client.print("Pressure (millibar)");
 +
          client.print("</td><td>");
 +
          Serial.print("Pressure (millibar): "); Serial.println(bme.getPressure_MB());
 +
          client.print(bme.getPressure_MB());
 +
          client.println("</td></tr>");
 +
 
 +
          client.print("<tr><td>");
 +
          client.print("Humidity (%rel)");
 +
          client.print("</td><td>");
 +
          Serial.print("Humidity (%rel): "); Serial.println(bme.getHumidity());
 +
          client.print(bme.getHumidity());
 +
          client.println("</td></tr>");
 +
 
 +
          client.print("<tr><td>");
 +
          client.print("Temperature (C)");
 +
          client.print("</td><td>");
 +
          Serial.print("Temperature (C): "); Serial.println(bme.getTemperature_C());
 +
          client.print(bme.getTemperature_C());
 +
          client.println("</td></tr>");
 +
 
 +
          client.println("</table>");
 +
         
 +
          client.println("</html>");
 +
          break;
 +
        }
 +
        if (c == '\n') {
 +
          // you're starting a new line
 +
          currentLineIsBlank = true;
 +
        } else if (c != '\r') {
 +
          // you've gotten a character on the current line
 +
          currentLineIsBlank = false;
 +
        }
 +
      }
 +
    }
 +
    // give the web browser time to receive the data
 +
    delay(1);
 +
    // close the connection:
 +
    client.stop();
 +
    Serial.println("client disconnected");
 +
  }
 +
}
 +
</pre>
 +
 
 +
== Sample Output ==
 +
 
 +
Here is a sample output from this evening, notably with no UV rays at all. The next days will be very sunny, so I'm looking forward to read the measurements.
 +
 
 +
<pre>
 +
Light Intensity: 285
 +
UVB1 (mW/cm2): 0.02
 +
UVB2 (mW/cm2): 0.02
 +
UV1 (mW/cm2): 0.02
 +
UV2 (mW/cm2): 0.02
 +
Pressure (millibar) 957.97
 +
Humidity (%rel) 58.39
 +
Temperature (C) 27.26
 +
</pre>
 +
 
 +
== Images ==
 +
 
 +
[[Image:2017-07-31-UV-Sensor1.jpg|400px]]
 +
[[Image:2017-07-31-UV-Sensor3.jpg|400px]]
 +
 
 +
[[Image:2017-07-31-UV-Sensor2.jpg|400px]]
 +
[[Image:2017-07-31-UV-Sensor4.jpg|400px]]
 +
 
 +
[[Image:2017-07-31-UV-Sensor5.jpg|400px]]
 +
[[Image:2017-07-31-UV-Sensor6.jpg|400px]]
 +
 
 +
[[Image:2017-07-31-UV-Sensor7.jpg|600px]]

Aktuelle Version vom 8. August 2017, 23:03 Uhr

1 Project Scope

When my daughter (my first child) was still a baby, my father bought a UV sensor so he could measure the UV intensity. Babys are very sensitive to UV light and the worst thing he could image was a baby sunburn.

So I thought, what about creating an alarm sensor that would measure UV intensity continuously. The result is this experiment, years later but no less outdated.

2 Solution Concept

I decided to build up the entire solution on the basis of a PoE 803.af Arduino Ethernet, including measurement of environment data and ambient light, including a solar cell based voltage verification.

There are pairs of ML8511 sensors, one pair simply behind the SiO (Silicium Oxide / Quarz glass), and another pair behind a lowpass UVB filter so that these two can measure exclusively UV-A. The UVB intensity is then the difference between the tutal power in this spectral region minus the UVA.

Here is a nice overview of sunburn effects:

2017-07-31-sunburn1.png

The sensor will display all measured values including a normalization on a web page. I am also interested in other environmental data (is the sensor doing fine?), so I decided to use a BME280 sensor also.

The ADS1115 has a programmable sensitivity feature, and the ML8511 runs on 3,3V. So I decided to use this 16 bit ADC on a Vmax basis of 4,096V, since the signal will not exceed 3,3V anyway. 3,28V is Vmax according to the sensor's spec and it will correlate with a brutal amount of 15mW/cm².

Here is the rough output scale of the sensor:

2017-07-31-UV intensity ml8511.png

3 Component List

  • Hammond 1550E aluminium diecast case: 50 €
  • 2sided base PCB: 2 €
  • Arduino Ethernet: 65 €
http://vampiresquidlabs.com/cms/shop/arduino-uno-ethernet/
  • Programmer: 3 €
  • Watterott BME280: 14 €
http://www.watterott.com/de/BME280-Breakout-Luftfeuchtigkeits-Druck-Tempertursensor
  • 4x Sparkfun ML8511: 4x 15€ = 60 €
https://learn.sparkfun.com/tutorials/ml8511-uv-sensor-hookup-guide
  • 1x Sparkfun TEMT6000: 5 €
https://www.sparkfun.com/products/8688
https://learn.sparkfun.com/tutorials/temt6000-ambient-light-sensor-hookup-guide
  • Adafruit ADS1115: 15 €
https://www.adafruit.com/product/1085
Arduino library: https://github.com/adafruit/Adafruit_ADS1X15
  • 2 solar cells: 5 €
  • screws, mounts: 10 €
  • Edmund Optics UVB lowpass: 80 €
Cut-Off Position λc (nm): 309±6
https://www.edmundoptics.com/optics/optical-filters/longpass-edge-filters/n-wg-305-50.8mm-sq.-longpass-filter/
  • Quarz glass to pass all wavelengths: 65 €
http://www.g-v-b.de
  • Silicon glue: 10 €
  • TP-Link PoE injector: 25

Total: ~400 €

4 Code

#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>
#include <Adafruit_ADS1015.h>
#include "cactus_io_BME280_I2C.h"

Adafruit_ADS1115 ads;  /* Use this for the 16-bit version */

// Create the BME280 object
BME280_I2C bme;              // I2C using default 0x77 
// or BME280_I2C bme(0x76);  // I2C using address 0x76

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 178, 177);

// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);
  server.begin();
  Serial.print("server is at ");
  Serial.println(Ethernet.localIP());

  // The ADC input range (or gain) can be changed via the following
  // functions, but be careful never to exceed VDD +0.3V max, or to
  // exceed the upper and lower limits if you adjust the input range!
  // Setting these values incorrectly may destroy your ADC!
  //                                                                ADS1015  ADS1115
  //                                                                -------  -------
  // ads.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 3mV      0.1875mV (default)
  ads.setGain(GAIN_ONE);           // 1x gain   +/- 4.096V  1 bit = 2mV      0.125mV
  // ads.setGain(GAIN_TWO);        // 2x gain   +/- 2.048V  1 bit = 1mV      0.0625mV
  // ads.setGain(GAIN_FOUR);       // 4x gain   +/- 1.024V  1 bit = 0.5mV    0.03125mV
  // ads.setGain(GAIN_EIGHT);      // 8x gain   +/- 0.512V  1 bit = 0.25mV   0.015625mV
  // ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV

  ads.begin();

  if (!bme.begin()) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }

  bme.setTempCal(-1);
}


//The Arduino Map function but for floats
//From: http://forum.arduino.cc/index.php?topic=3922.0
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

void loop() {
  // listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    Serial.println("new client");
    // an http request ends with a blank line
    boolean currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);
        // if you've gotten to the end of the line (received a newline
        // character) and the line is blank, the http request has ended,
        // so you can send a reply
        if (c == '\n' && currentLineIsBlank) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will be closed after completion of the response
          client.println("Refresh: 5");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          
          client.println("<table>");
          
          client.print("<tr><td>");
          client.print("Light Intensity:");
          client.print("</td><td>");
          int sensorReading = analogRead(0);
          Serial.print("Light Intensity: "); Serial.println(sensorReading);
          client.print(sensorReading);
          client.println("</td></tr>");

          int16_t adc0, adc1, adc2, adc3;
          float adc0_1, adc1_1, adc2_1, adc3_1;
          float uvIntensity_0, uvIntensity_1, uvIntensity_2, uvIntensity_3;
          
          client.print("<tr><td>");
          client.print("UVB1 (mW/cm2):");
          client.print("</td><td>");
          adc0 = ads.readADC_SingleEnded(0);
          adc0_1 = (float)adc0 / 8000;
          uvIntensity_0 = mapfloat(adc0_1, 0.987, 2.8, 0.0, 15.0);
          Serial.print("UVB1: "); Serial.println(uvIntensity_0);
          client.print(uvIntensity_0);
          client.println("</td></tr>");

          client.print("<tr><td>");
          client.print("UVB2 (mW/cm2):");
          client.print("</td><td>");
          adc1 = ads.readADC_SingleEnded(1);
          adc1_1 = (float)adc1 / 8000;
          uvIntensity_1 = mapfloat(adc1_1, 0.999, 2.8, 0.0, 15.0);
          Serial.print("UVB2: "); Serial.println(uvIntensity_1);
          client.print(uvIntensity_1);
          client.println("</td></tr>");

          client.print("<tr><td>");
          client.print("UV1 (mW/cm2):");
          client.print("</td><td>");
          adc2 = ads.readADC_SingleEnded(2);
          adc2_1 = (float)adc2 / 8000;
          uvIntensity_2 = mapfloat(adc2_1, 0.989, 2.8, 0.0, 15.0);
          Serial.print("UV1: "); Serial.println(uvIntensity_2);
          client.print(uvIntensity_2);
          client.println("</td></tr>");

          client.print("<tr><td>");
          client.print("UV2 (mW/cm2):");
          client.print("</td><td>");
          adc3 = ads.readADC_SingleEnded(3);
          adc3_1 = (float)adc3 / 8000;
          uvIntensity_3 = mapfloat(adc3_1, 0.999, 2.8, 0.0, 15.0);
          Serial.print("UV2: "); Serial.println(uvIntensity_3);
          client.print(uvIntensity_3);
          client.println("</td></tr>");

          bme.readSensor(); 
          
          client.print("<tr><td>");
          client.print("Pressure (millibar)");
          client.print("</td><td>");
          Serial.print("Pressure (millibar): "); Serial.println(bme.getPressure_MB());
          client.print(bme.getPressure_MB());
          client.println("</td></tr>");

          client.print("<tr><td>");
          client.print("Humidity (%rel)");
          client.print("</td><td>");
          Serial.print("Humidity (%rel): "); Serial.println(bme.getHumidity());
          client.print(bme.getHumidity());
          client.println("</td></tr>");

          client.print("<tr><td>");
          client.print("Temperature (C)");
          client.print("</td><td>");
          Serial.print("Temperature (C): "); Serial.println(bme.getTemperature_C());
          client.print(bme.getTemperature_C());
          client.println("</td></tr>");

          client.println("</table>");
          
          client.println("</html>");
          break;
        }
        if (c == '\n') {
          // you're starting a new line
          currentLineIsBlank = true;
        } else if (c != '\r') {
          // you've gotten a character on the current line
          currentLineIsBlank = false;
        }
      }
    }
    // give the web browser time to receive the data
    delay(1);
    // close the connection:
    client.stop();
    Serial.println("client disconnected");
  }
}

5 Sample Output

Here is a sample output from this evening, notably with no UV rays at all. The next days will be very sunny, so I'm looking forward to read the measurements.

Light Intensity:	285
UVB1 (mW/cm2):	0.02
UVB2 (mW/cm2):	0.02
UV1 (mW/cm2):	0.02
UV2 (mW/cm2):	0.02
Pressure (millibar)	957.97
Humidity (%rel)	58.39
Temperature (C)	27.26

6 Images

2017-07-31-UV-Sensor1.jpg 2017-07-31-UV-Sensor3.jpg

2017-07-31-UV-Sensor2.jpg 2017-07-31-UV-Sensor4.jpg

2017-07-31-UV-Sensor5.jpg 2017-07-31-UV-Sensor6.jpg

2017-07-31-UV-Sensor7.jpg