RobTillaart / ML8511

Arduino library for ML8511 UV sensor
MIT License
10 stars 3 forks source link

Q: ML8511 DB_Index example #11

Closed RobTillaart closed 1 week ago

RobTillaart commented 1 month ago
          I tried to use example "ML8511 DB_Index" but i got the negative result. how to fixed it and how to calibrate the sensor?

Originally posted by @nstgn in https://github.com/RobTillaart/ML8511/issues/5#issuecomment-2264631691

RobTillaart commented 1 month ago

@nstgn As the PR is already merged I created a new issue to keep discussions clear.

RobTillaart commented 1 month ago

@nstgn I assume ML8511 DB_Index ==> ML8511_DUV_index.

Can you tell what board you are using?

My first assumption is that you need to call setVoltsPerStep() with right parameters.

RobTillaart commented 1 month ago

but i got the negative result.

Can you elaborate on your measurements? What board did you use? Can you post the code you used?


Analysis

float ML8511::estimateDUVindex(float mWcm2)
{
  //  rewrite in 0.1.6
  //  https://github.com/RobTillaart/ML8511/issues/4
  return mWcm2 * _DUVfactor;
}

The default _DUVfactor == 1.61 - see reset() therefor estimateDUVindex() can only return a negative value if the parameter mWcm2 is negative. The library could be enhanced to check on that.

In the example code the mWcm2 parameter is the return value of getUV() which cannot return a negative value as it is prohibited by the voltage2mW(float voltage) function. Negative values, in fact all below and including 1.0, are clipped to zero.

float ML8511::getUV(uint8_t energyMode)
{
  if (!_enabled)
  {
    enable();
    //  datasheet page 5 states wait for max 1 millisecond
    uint32_t start = micros();
    while (micros() - start < 1000) yield();
  }
  //  read the sensor
  float voltage = analogRead(_analogPin) * _voltsPerStep;
  //  go to low power mode?
  if (energyMode == LOW)
  {
    disable();
  }

  return voltage2mW(voltage);
}

//  to be used by external ADC
float ML8511::voltage2mW(float voltage)
{
  //  see datasheet - page 4
  //  mW/cm2 @ 365 nm
  //  @ 25 Celsius
  //  formula estimated on graph
  if (voltage <= 1.0)
  {
    return 0.0;
  }
  voltage -= 1.0;    // subtract for zero point.
  float mWcm2 = voltage * (15.0 / 1.8);
  return mWcm2;
}

Notes to myself,

nstgn commented 1 month ago

Thank you for your response and explanation. I am using an Arduino Mega board. I checked again and it turns out you were right, I get zero value in the dark. The mistake was on my part. Your explanation was very helpful. I will check again my measurement, and I'll try to use ESP32 and may have some additional question later, including about calibration.

nstgn commented 1 month ago

I've tried again, and the value is better than before. The UV reading is 0.0163, and the index is 0. However, in the dark, I get a UV reading of 0. When I switch to an ESP32 board, I get a UV reading of up to 10 and an index of up to 20. I need your help to properly convert the code to work with the ESP32, as I haven't been able to do it successfully on my own. Thankyou

RobTillaart commented 1 month ago

The ADC of the ESP32 is 3.3V and is 12 bit compared to the mega 5V and 10 bit.

Inside home with no artificial light there is normally no UV, so you should read 0. Light bulbs, tube lights or leds might give some UV, you can confirm this by moving the sensor closer to the UV source. I expect that the inverse square law applies for the intensity, so 2x close is 4x more UV. Might explain the 0.0163

For the ESP32 the settings must be void setVoltsPerStep(3.3, 4095), this needs to be added to your code.

Note that the ESP32 ADC has a non linear behavior at both ends of the scale. If this is your working range you should use another (external) ADC. See - https://w4krl.com/esp32-analog-to-digital-conversion-accuracy/ for detailed info.

nstgn commented 1 month ago
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Arduino.h>
#include <ML8511.h>
#define ANALOGPIN     15
#define ENABLEPIN     4
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1
Adafruit_SSD1306 display(OLED_RESET);
ML8511 light(ANALOGPIN, ENABLEPIN);

void setup()
{
  Serial.begin(115200);
  Serial.println("UV UltraViolet ML8511");
   // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }
  // manually enable / disable the sensor.
  light.enable();
  light.setVoltsPerStep(3.3, 4095);

  light.setDUVfactor(1.80);    // calibrate your sensor
  Serial.print("\tmW cm^2");
  Serial.print("\t Index");
  Serial.println();
}

void loop()
{
  float UV = light.getUV();
  float UVI = light.estimateDUVindex(UV);
  Serial.print(UV, 4);
  Serial.print("\t");
  Serial.print(UVI, 1);
  Serial.println();
  delay(1000);
// Draw vertical line in the middle of the display
  display.drawLine(SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2, SCREEN_HEIGHT, WHITE);
  display.setCursor(0,0);  //oled display
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.println("Intensity");

  display.setCursor(8,13);  //oled display
  display.setTextSize(1.9);
  display.setTextColor(WHITE);
  display.println(UV, 4);

  display.setCursor(5,25);  //oled display
  display.setTextSize(1.5);
  display.setTextColor(WHITE);
  display.println("mW/cm^2");
    // Display DUV Index
  display.setCursor(SCREEN_WIDTH / 2 + 23, 0); // Adjusted cursor position to the right of the line
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.println("Index");
  display.setCursor(SCREEN_WIDTH / 2 + 26, 15); // Adjusted cursor position to the right of the line
  display.setTextSize(1.5);
  display.setTextColor(WHITE);
  display.println(UVI, 1);
  display.display();
  delay(300);
  display.clearDisplay();
}

"I added an OLED display to show the results, and now I suspect there's an issue with the calibration values. Is this correct?"

(added code tags for syntax highlighting)

RobTillaart commented 1 month ago

and now I suspect there's an issue with the calibration values. Is this correct?

Could be, this line of code is the only calibration the library offers (except from adjusting to the ADC)

  light.setDUVfactor(1.80);    // calibrate your sensor

You must make measurements and compare those with local weather stations. Airports often provide quite detailed info about radiation. You should look for both the mW per cm2 and the UV-index (which is a rough scale). From those you can calculate the DUVfactor needed,

The mW per cm2 should be quite the same, best take average of a dozen measurements with e.g. 5 minutes in between. It is also important to point the sensor to the sun. See - https://en.wikipedia.org/wiki/Lambert%27s_cosine_law This cosine law can easily be verified by rotating the sensor with respect to the UV source. The values should go up/down.

Note: this library has no correction functions for Lamberts cosine law. This one has experimental support => https://github.com/RobTillaart/BH1750FVI_RT

Hope this helps

nstgn commented 1 month ago

image I found this formula from the link https://blog.otthydromet.com/en/what-is-the-uv-index-and-how-to-calculate-it/ but the result is different when I try to calculate it manually using the sensor readings

RobTillaart commented 1 month ago

Thanks for the link, not read in detail but that formula has a specific context (== specific device)

From UV-E radiation measurements, the UV-Index is calculated as follow: Take the output from the UV-E radiometer according to ISO 17166:1999/CIE S007/E-1998. Transform the output voltage to W/m² with the instrument’s sensitivity, for example 0.4 Volt, which corresponds to an erythemal radiation value of 0.0675 W/m². Multiply it with the factor of 40 m²/W to get the UVI value, in this case 2.7.

You cannot use formulas from sensor type A (cheap ML8511) for math in sensor B (professional UV sensor) or vice versa

About the ML8511, this might be helpful

RobTillaart commented 2 weeks ago

@nstgn If this issue is solved (I saw a lot of answers on the Arduino Forum) you may close it.

RobTillaart commented 1 week ago

Seems solved.