Hardware | ML8511 UV sensor 를 가지고 놀아보자

|

1. 여름


유독 2018년의 여름은 예년보다 더욱 덥습니다.

지구 온실효과로 내년 뉴스에도, "올해도 최고 온도를 갱신했습니다!" 라는 멘트를 들을 수 있을것 같습니다.


오존층 파괴등으로 인하여 피부에 직사광선을 쬐는 것은 이제 위험한 시대입니다.

자외선 - Ultraviolet 인거죠.



위 표는 UV 세기에 따라 어떤식으로 몸을 보호해야 하는지 나타내주는 표 입니다.

더이상 선글라스는 패션이 아니군요.





2. ML8511 센서 구매


UV 를 측정해주는 아나로그 센서로는 ML8511 이 있습니다.

AliExpress 를 검색하니 아래가 가장 많이 팔렸던 제품입니다.


* GY-8511 ML8511 UVB Breakout Test Module Ray Sensor UV Detector Analog Output Module

https://www.aliexpress.com/item/GY-8511-ML8511-UVB-Breakout-Test-Module-Ray-Sensor-UV-Detector-Analog-Output-Module/32451073500.html



기능은... 280-390nm 를 가장 잘 측정해 준다 합니다.


The ML8511 breakout is an easy to use ultraviolet light sensor.

The MP8511 UV (ultraviolet) Sensor works by outputing an analog signal in relation to the amount of UV light that's detected.

This breakout can be very handy in creating devices that warn the user of sunburn or detect the UV index as it relates to weather conditions.


This sensor detects 280-390nm light most effectively.

This is categorized as part of the UVB (burning rays) spectrum and most of the UVA (tanning rays) spectrum.

It outputs a analog voltage that is linearly related to the measured UV intensity (mW/cm2).

If your microcontroller can do an analog to digital signal conversion then you can detect the level of UV!


이 구간은 피부를 가장 잘 타게 만드는 구간이라고 하네요.







3. ML8511 도착


뭐 특별히 이상한 점 없이 잘 도착 하였습니다.



이렇게 생겼습니다.



다리를 납땜해줍니다.



숫자가 적힌 부분은 마무리가 되지 않은 부분이므로, 니퍼로 제거해 줍니다.



센서를 확대해 봤습니다.

금으로 된 점퍼가 모든 단자에 연결되어 있지 않은 것을 보니, 가지고 있는 성능을 완전히 구현해 놓은 것은 아닌것 같군요.





4. Layout / Pinout


Pinout 은 다음과 같습니다.


   ML8511  | Arduino Micro
---------------------------
    3V3    |    A1 / 3.3V
    GND    |      GND
    OUT    |      A0
    EN     |    A1 / 3.3V
---------------------------


SparkFun 의 다음 link 가 도움이 됩니다.


* ML8511 UV Sensor Hookup Guide

https://learn.sparkfun.com/tutorials/ml8511-uv-sensor-hookup-guide


다만, 중국제는 pin 수가 하나 더 많아서 헷갈릴 수 있습니다.

결국 같은 pinout 이지만, 동일한 GY8511 을 사용한 다음 blogpost 가 더 도움이 되었습니다.


* GYM8511ML UV Sensor Connection To Arduino Nano

http://twenty5nov.blogspot.com/2016/02/gym8511ml-uv-sensor-connection-to.html



Layout 은 다음과 같습니다.






5. sketch


위의 SparkFun 사이트의 소스를 사용해 봅니다.


 /* 
 ML8511 UV Sensor Read Example
 By: Nathan Seidle
 SparkFun Electronics
 Date: January 15th, 2014
 License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).

 The ML8511 UV Sensor outputs an analog signal in relation to the amount of UV light it detects.

 Connect the following ML8511 breakout board to Arduino:
 3.3V = 3.3V
 OUT = A0
 GND = GND
 EN = 3.3V
 3.3V = A1
 These last two connections are a little different. Connect the EN pin on the breakout to 3.3V on the breakout.
 This will enable the output. Also connect the 3.3V pin of the breakout to Arduino pin 1.

 This example uses a neat trick. Analog to digital conversions rely completely on VCC. We assume
 this is 5V but if the board is powered from USB this may be as high as 5.25V or as low as 4.75V:
 http://en.wikipedia.org/wiki/USB#Power Because of this unknown window it makes the ADC fairly inaccurate
 in most cases. To fix this, we use the very accurate onboard 3.3V reference (accurate within 1%). So by doing an
 ADC on the 3.3V pin (A1) and then comparing this against the reading from the sensor we can extrapolate
 a true-to-life reading no matter what VIN is (as long as it's above 3.4V).

 Test your sensor by shining daylight or a UV LED: https://www.sparkfun.com/products/8662

 This sensor detects 280-390nm light most effectively. This is categorized as part of the UVB (burning rays)
 spectrum and most of the UVA (tanning rays) spectrum.

 There's lots of good UV radiation reading out there:
 http://www.ccohs.ca/oshanswers/phys_agents/ultravioletradiation.html
 https://www.iuva.org/uv-faqs

*/

//Hardware pin definitions
int UVOUT = A0; //Output from the sensor
int REF_3V3 = A1; //3.3V power on the Arduino board

void setup() {
	Serial.begin(9600);
	
	pinMode(UVOUT, INPUT);
	pinMode(REF_3V3, INPUT);
	
	Serial.println("ML8511 example");
}

void loop() {
	int uvLevel = averageAnalogRead(UVOUT);
	int refLevel = averageAnalogRead(REF_3V3);
	
	//Use the 3.3V power pin as a reference to get a very accurate output value from sensor
	float outputVoltage = 3.3 / refLevel * uvLevel;
	
	float uvIntensity = mapfloat(outputVoltage, 0.99, 2.8, 0.0, 15.0); //Convert the voltage to a UV intensity level
	
	Serial.print("output: ");
	Serial.print(refLevel);
	
	Serial.print("\tML8511 output: ");
	Serial.print(uvLevel);
	
	Serial.print("\tML8511 voltage: ");
	Serial.print(outputVoltage);
	
	Serial.print("\tUV Intensity (mW/cm^2): ");
	Serial.print(uvIntensity);
	
	Serial.println();
	
	delay(100);
}

//Takes an average of readings on a given pin
//Returns the average
int averageAnalogRead(int pinToRead) {
	byte numberOfReadings = 8;
	unsigned int runningValue = 0;
	
	for(int x = 0 ; x < numberOfReadings ; x++)
		runningValue += analogRead(pinToRead);
	runningValue /= numberOfReadings;
	
	return(runningValue);
}

//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;
}


Serial out 으로 보면 reLevel / uvLevel / outputVoltage / uvIntensity 를 아래와 같이 확인할 수 있습니다.



뭔가 와닿지 않죠?





6. OLED 로 UV Index 표시하기


Youtube 를 보니 UV Index 를 LCD 에 표현해 주는 분이 계셨습니다.


* Arduino UV Meter using the UV31A Ultraviolet Sensor

http://www.electronics-lab.com/project/arduino-uv-meter-using-uv30a-ultraviolet-sensor/



센서와 LCD 스크린도 다르지만 컨셉은 이해할 수 있었습니다.



따라하지 아니할 수가 없네요.

저는 SSD1306 과 ML8511 을 사용해서 소스를 converting 해 봅니다.


우선 처음 부팅시 보여주는 logo 를 만드는 작업을 하였습니다.

너무 길어지니 따로 포스팅 하였습니다.


* Hardware | SSD1306 에 로고를 세겨보자

http://chocoball.tistory.com/entry/Hardware-SSD1306-create-logo


표시해주는 UV Index 는 UV31A 센서가 기준이라, 가지고 있는 ML8511 로 하면 전혀 다른 숫자들이 나옵니다.


찾고 찾아보니, 중국에서 ML8511 을 가지고 UV Index 를 테이블화 하여 작성된 논문이 있었습니다.


* See UV on Your Skin: An Ultraviolet Sensing and Visualization System

BodyNets_2013_UV.pdf


논문 안에 기재된, 아래 테이블을 가지고 적용하면 되겠네요!



Vcc = 3.0V 이고, ML8511 은 3.3V 이지만, 소스에서 보정을 해주므로 그리 큰 문제는 되지 않을것 같습니다.


다음이 완성된 최종 sketch 입니다.

LOGO를 새기는 부분과 "else if" 를 이용한 UV Indexing 부분으로 나뉘어 있습니다.

나머지는 SparkFun 의 소스도 사용했구요. 한마디로 짜집기 버전입니다.


#include "SPI.h"
#include "Wire.h"
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
  
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

static const unsigned char PROGMEM UVMeter[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x18, 0x03, 0x18, 0x01, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x1C, 0x03, 0x18, 0x03, 0x80, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x1C, 0x03, 0x1C, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x07, 0xC0, 0x07, 0x00, 0x00, 0x1C, 0x03, 0x0C, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC0, 0x0F, 0xC0, 0x0E, 0x00, 0x00, 0x1C, 0x03, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0x0F, 0xE0, 0x3E, 0x00, 0x00, 0x1C, 0x03, 0x0E, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xF0, 0x0F, 0xF0, 0x7E, 0x00, 0x00, 0x1C, 0x03, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFC, 0x0F, 0xF8, 0xFE, 0x00, 0x00, 0x1C, 0x03, 0x06, 0x0C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xDF, 0xFD, 0xFE, 0x00, 0x00, 0x1C, 0x03, 0x07, 0x0C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1C, 0x03, 0x03, 0x0C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x1C, 0x03, 0x03, 0x18, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x1C, 0x03, 0x03, 0x98, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0xE0, 0x03, 0xFF, 0x00, 0x00, 0x1C, 0x07, 0x01, 0x98, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7F, 0x03, 0xE0, 0x7F, 0x00, 0x00, 0x0C, 0x06, 0x01, 0xB0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFC, 0x3F, 0xFE, 0x1F, 0x3F, 0xF0, 0x0E, 0x0E, 0x01, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x06, 0x0F, 0xF8, 0xFF, 0xFF, 0x8F, 0xFF, 0xC0, 0x07, 0xFC, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x07, 0xFF, 0xF1, 0xFF, 0xFF, 0xC7, 0xFF, 0x80, 0x03, 0xF8, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x03, 0xFF, 0xE7, 0xFF, 0xFF, 0xF3, 0xFF, 0x00, 0x00, 0x00, 0x60, 0x18, 0x00, 0x00, 0x00, 0x00,
0x03, 0xFF, 0xCF, 0xFF, 0xFF, 0xF9, 0xFF, 0x00, 0x00, 0x00, 0x70, 0x38, 0x00, 0x20, 0x00, 0x00,
0x01, 0xFF, 0x9F, 0xFF, 0x7F, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x70, 0x38, 0x00, 0x20, 0x00, 0x00,
0x00, 0xFF, 0x9F, 0xF0, 0x07, 0xFC, 0xFE, 0x00, 0x00, 0x00, 0x58, 0x28, 0x38, 0x78, 0x38, 0x48,
0x00, 0x7F, 0x3F, 0xC0, 0x01, 0xFE, 0x7E, 0x00, 0x00, 0x00, 0x58, 0x68, 0x6E, 0x78, 0xCC, 0x7C,
0x00, 0x3E, 0x7F, 0x80, 0x00, 0xFF, 0x3E, 0x00, 0x00, 0x00, 0x48, 0x48, 0xC6, 0x20, 0x84, 0x60,
0x00, 0x1E, 0x7F, 0x00, 0x00, 0x7F, 0x3C, 0x00, 0x00, 0x00, 0x4C, 0xC8, 0xC2, 0x21, 0x86, 0x40,
0x00, 0x1E, 0x7E, 0x00, 0x00, 0x3F, 0x3C, 0x00, 0x00, 0x00, 0x4C, 0x88, 0xFE, 0x21, 0xFE, 0x40,
0x00, 0x3C, 0xFE, 0x00, 0x00, 0x3F, 0x9F, 0x00, 0x00, 0x00, 0x46, 0x88, 0x80, 0x21, 0x80, 0x40,
0x00, 0x7C, 0xFC, 0x00, 0x00, 0x1F, 0x9F, 0xF0, 0x00, 0x00, 0x47, 0x88, 0xC0, 0x21, 0x80, 0x40,
0x00, 0xFC, 0xFC, 0x00, 0x00, 0x1F, 0x9F, 0xFC, 0x00, 0x00, 0x43, 0x08, 0x42, 0x30, 0xC0, 0x40,
0x03, 0xFC, 0xFC, 0x00, 0x00, 0x1F, 0x9F, 0xFE, 0x00, 0x00, 0x43, 0x08, 0x7E, 0x1C, 0x7C, 0x40,
0x07, 0xFC, 0xFC, 0x00, 0x00, 0x1F, 0x9F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

String UV = "0"; 

//Hardware pin definitions
int UVOUT = A0; //Output from the sensor
int REF_3V3 = A1; //3.3V power on the Arduino board

void setup() {
	pinMode(UVOUT, INPUT);
	pinMode(REF_3V3, INPUT);
	
	display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
	display.clearDisplay();
	display.drawBitmap(0, 0, UVMeter, 128, 32, WHITE);
	display.display();
	
	delay(3000);
}

 
void loop() {
	int stringLength = 0;
	UV = readSensor();
	
	display.clearDisplay();
	display.drawRect(0, 0, display.width(), display.height(), WHITE);
	display.drawFastHLine(0, 10, 128, WHITE);
	
	display.setTextSize(1);
	display.setTextColor(WHITE);
	display.setCursor(40, 2);
	display.print("UV INDEX");
	
	stringLength = UV.length();
	printUV(stringLength);
	display.display();
	
	delay(150);
}

void printUV(int length) {
	switch(length) {
		case 1:
			display.setTextSize(3);
			display.setTextColor(WHITE);
			display.setCursor(60, 10);
			display.print(UV); break;
		case 2:
			display.setTextSize(3);
			display.setTextColor(WHITE);
			display.setCursor(55, 10);
			display.print(UV); break;
		default: display.print(UV); break;
	}
}

String readSensor() {
	String UVIndex = "0";
	
	int uvLevel = averageAnalogRead(UVOUT);
	int refLevel = averageAnalogRead(REF_3V3);
	
	//Use the 3.3V power pin as a reference to get a very accurate output value from sensor
	float voltage = 3.3 / refLevel * uvLevel;
		
	if(voltage<=0.993) {
		UVIndex = "0";
	} else if (voltage>0.993 && voltage<=1.073) {
		UVIndex = "1";
	} else if (voltage>1.073 && voltage<=1.153) {
		UVIndex = "2";
	} else if (voltage>1.153 && voltage<=1.233) {
		UVIndex = "3";
	} else if (voltage>1.233 && voltage<=1.313) {
		UVIndex = "4";
	} else if (voltage>1.313 && voltage<=1.393) {
		UVIndex = "5";
	} else if (voltage>1.393 && voltage<=1.473) {
		UVIndex = "6";
	} else if (voltage>1.473 && voltage<=1.553) {
		UVIndex = "7";
	} else if (voltage>1.553 && voltage<=1.633) {
		UVIndex = "8";
	} else if (voltage>1.633 && voltage<=1.713) {
		UVIndex = "9";
	} else if (voltage>1.713 && voltage<=1.793) {
		UVIndex = "10";
	} else if (voltage>1.793 && voltage<=1.873) {
		UVIndex = "11";
	} else if (voltage>1.873 && voltage<=1.953) {
		UVIndex = "12";
	} else if (voltage>1.953 && voltage<=2.033) {
		UVIndex = "13";
	} else if (voltage>2.033 && voltage<=2.113) {
		UVIndex = "14";
	} else if (voltage>2.113 && voltage<=2.193) {
		UVIndex = "15";
	} else if (voltage>2.193 && voltage<=2.273) {
		UVIndex = "16";
	} else if (voltage>2.273 && voltage<=2.353) {
		UVIndex = "17";
	} else if (voltage>2.353 && voltage<=2.433) {
		UVIndex = "18";
	} else if (voltage>2.433 && voltage<=2.513) {
		UVIndex = "19";
	} else if (voltage>2.513 && voltage<=2.593) {
		UVIndex = "20";
	} else if (voltage>2.593) {
		UVIndex = "21";
	}
	
	return UVIndex;
}

//Takes an average of readings on a given pin
//Returns the average
int averageAnalogRead(int pinToRead) {
	byte numberOfReadings = 8;
    unsigned int runningValue = 0;
     
    for(int x = 0 ; x < numberOfReadings ; x++)
        runningValue += analogRead(pinToRead);
    runningValue /= numberOfReadings;
     
    return(runningValue);
}






7. 밖에 나가서 확인해 보자


저녁 6시가 되니, 해가 뉘엿뉘엿 지고 있습니다.

해떨어지기 전에 얼른 나가서 확인해 봅니다.



실내에서는 당연히 UV Index = 0 입니다.



저무는 해를 향해 조준합니다.



오옷!

3 ~ 4 정로를 보여주네요.



동영상 갑니다.


지역은 다르지만 New York 의 월별/시간대별 UV Index 그래프 입니다.

오후 4시만 되어도 3 이하로 떨어지는군요.


현재 한국은 한여름이고, 오후 6시라도 햇볕의 기운이 아직 남아 있으므로, "3 ~ 4" 는 적절하게 인식한것 같습니다.



집에 resin 을 굳히는 UV lamp 가 있다는걸 뒤늦게 생각해 내어,

UV lamp 로도 확인해 봅니다.



해지는 햇살과 비슷하게 3 ~ 4 정도를 나타내네요.





FIN


UV sensor 인 ML8511 을 가지고 알차게 실험해 봤네요.

이참에 OLED 에 로고 새기는 방법도 터득했구요.


해가 거듭될수록 자외선의 위험성이 강조되는 요즈음,

향후를 위해서라도 한번쯤은 확인해 보고 싶었던 센서 였습니다.


다른 분들도 참고가 되었으면 좋겠습니다.


And