Components and supplies
Resistor 10k ohm
Jumper wires (generic)
Tactile Switch, Top Actuated
128x64 OLED display module
TP4056 charging module
Arduino Nano R3
18650 Battery holder
UV sensor
18650 4.2 V lithium battery
Tools and machines
Soldering iron (generic)
Solder Wire, 0.02" Diameter
PCB Holder, Soldering Iron
Project description
Code
UV meter code
c_cpp
1/* 2 UV meter code 3 4 This is the code for the portable UV meter. Most of it is essentially 5 code for the OLED 0.96" display, to draw and print the desired info on it. 6 There is also a setup for the tactile switch, and readings of the actual UV sensor 7 8 The circuit: 9 * OLED display: SCL attached to A5 and SDA attached to A4 10 * UV sensor: sensor's OUT pin is connected to A0 11 * Tactile Switch: The tactile switch is connected D12 12 13 Feel free to use this code and modify it. Just keep it "open source" and share it with the community. That's what this project is all about, after all! 14 15 Created and modified throughout the whole of 2022 16 By Davi Salles Leite, d4visl on the Arduino Hub 17 Modifications (dd/mm/yy): 18 19 https://create.arduino.cc/projecthub/d4visl/portable-and-rechargeable-ultraviolet-uv-radiation-meter-751c2e?auth_token=740cf17a480a86a9b0df5523606d24a4 20 21*/ 22int i = 0; // Variable to store values 23int button = 12; // Pin of the tactile switch used to change modes 24unsigned long t_1 = millis(); // records millis for the sun animation on the OLED display 25unsigned long t_2 = millis(); // same as the above 26int mode = 0; // sets the initial mode 27 28// include the necessary libraries 29#include <Wire.h> 30#include <Adafruit_GFX.h> 31#include <Adafruit_SSD1306.h> 32 33#define SCREEN_WIDTH 128 // OLED display width, in pixels 34#define SCREEN_HEIGHT 64 // OLED display height, in pixels 35 36// declare an SSD1306 display object connected to I2C 37Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); 38 39void setup() { 40 Serial.begin(9600); 41 42 // initialize OLED display with address 0x3C for 128x64 43 if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { 44 Serial.println(F("SSD1306 allocation failed")); 45 while (true); 46 } 47 48 delay(2000); // wait for initializing 49 display.clearDisplay(); // clear display 50 51 pinMode(button, INPUT); // Initialize 52} 53 54void loop() { 55 56 // Read the tactile switch for input. If high, then the switch is being pressed. 57 if (digitalRead(button) == HIGH){ 58 mode += 1; // change the value of mode 59 delay(500); 60 } 61 62 // Change the mode 63 if (mode == 0){ 64 mode_0(); // call mode 0 65 } 66 else if (mode == 1){ 67 mode_1(); // call mode 1 68 } 69 else if (mode == 2){ 70 mode_2(); // call mode 2 71 } 72 else{ 73 mode = 0; // revert the value of mode to the initial and calls mode 0 74 mode_0(); 75 } 76} 77 78/* 79 Mode 0 80 Mode 0 shows the following information: 81 * Analog value measured by the sensor - This varies between 0-1023, 82 but the maximum value ever registered during testing was 350 83 * A graph to demonstrate the analog value through a visual aid 84 * Animation of the sun, for aesthetic purposes 85 86*/ 87void mode_0(){ 88 89 /* This is an animation of the sun. It starts by drawing a filled in circle in the top left, 90 then every time this mode is called, it draws 4 circumferences in a progressively outwards motion, to represent the sunlight moving away from the sun. 91 This animation is repeated every few seconds, and pauses from time to time. 92 millis() was used to keep track of this time.*/ 93 if (millis() - t_1 > 300){ 94 i += 1; 95 t_1 = millis(); 96 display.clearDisplay(); 97 98 if (i >= 4){ 99 i = 0; 100 } 101 if (millis() - t_2 < 2000){ 102 display.drawCircle(0,0,24,WHITE); 103 display.drawCircle(0,0,28,WHITE); 104 } 105 else{ 106 if (millis() - t_2 > 7000){ 107 t_2 = millis(); 108 } 109 110 display.drawCircle(0,0,16 + (i * 2),WHITE); 111 display.drawCircle(0,0,20 + (i * 2),WHITE); 112 113 if (i < 2){ 114 display.drawCircle(0,0,24 + (i * 2), WHITE); 115 } 116 117 if (i < 1){ 118 display.drawCircle(0,0,28 + (i * 2),WHITE); 119 } 120 } 121 122 display.fillCircle(0,0,20,WHITE); // this is the circle on the top left 123 124 display.drawRoundRect(105, 10, 20, 45, 3, WHITE); // body of the graphic 125 126 // Set all the desired specs, such as color and size 127 display.setTextColor(WHITE); 128 display.setTextSize(1); 129 130 // Set cursor to desired position to print a part of the graphic 131 display.setCursor(106,0); 132 display.println("MAX"); 133 134 // Set cursor to desired position to print a part of the graphic 135 display.setCursor(106,57); 136 display.println("MIN"); 137 138 // Set specs for text 139 display.setTextColor(WHITE); 140 display.setTextSize(3); 141 142 // Set cursor to desired position to print text 143 display.setCursor(SCREEN_WIDTH / 2 - 10, 0); 144 display.println("UV"); 145 146 float media = 0; // variable used to calculate the mean of a series of 5 values 147 148 // read the sensor 5 times and find the sum of the values 149 for (int j = 0; j < 5; j++){ 150 int sensor_value = analogRead(A0); // read the sensor pin 151 media += sensor_value; // add value to media 152 } 153 154 Serial.println(media); // print media to serial monitor 155 156 // find the mean of the measured values, and round the result, to diminish the effect of current oscillations 157 media = media/5; 158 media = round(media); 159 160 // print mean to serial monitor 161 Serial.print("Media:"); 162 Serial.println(media); 163 164 // set the cursor depending on the number of algarisms, so that the number is centralized 165 if (media <= 9){ 166 display.setCursor(63,30); 167 } 168 else if(media <= 99){ 169 display.setCursor(54,30); 170 } 171 else{ 172 display.setCursor(42,30); 173 } 174 175 display.print(int(media)); // print the rounded mean sensor value to the display 176 177 display.setTextSize(1); // change text size 178 179 // draw 4 lines to separate the levels of the graphic 180 for (int j = 9; j <= 36 ; j += 9 ){ 181 display.fillRect(105, 55 - j, 20, 1, WHITE); // what is a line if not a rectangle of height = 1 pixel? 182 } 183 184 // fill the levels of the graphic, according to the media (rounded mean value of the sensor's readings) 185 if(media > 0){ 186 // "Very high" on the UV index scale 187 if(media >= 290){ 188 display.fillRoundRect(105, 10, 20, 45, 3, WHITE); 189 } 190 // "High" on the UV index scale 191 else if(media >= 256){ 192 display.fillRoundRect(105, 19, 20, 36, 3, WHITE); 193 } 194 // "Moderate" on the UV index scale 195 else if(media >= 178){ 196 display.fillRoundRect(105, 28, 20, 27, 3, WHITE); 197 } 198 // "Low" on the UV index scale 199 else if(media >= 121){ 200 display.fillRoundRect(105, 37, 20, 18, 3, WHITE); 201 } 202 // Any UV radiation detected (0 < UV < 1) 203 else{ 204 display.fillRoundRect(105, 46, 20, 9, 3, WHITE); 205 } 206 } 207 208 display.display(); // display all the drawn information 209 } 210} 211 212/* 213 214 Mode 1 215 Mode 1's purpose is to showcase the approximation of the UV index, along with a visual aid (graph), 216 and the risk of harm from unprotected sun exposure, for the average adult. 217 218*/ 219 220void mode_1(){ 221 float media = 0; // variable used to calculate the mean of a series of 5 values 222 223 // clear display and change cursor position and text size 224 display.clearDisplay(); 225 display.setCursor(0,0); 226 display.setTextSize(1); 227 display.println("UV INDEX"); // print desired text 228 229 display.drawTriangle(93,59,66,5,120,5, WHITE); // draw body of the graph 230 231 // draw the lines of the graph 232 for (int j = 0; j < 9; j ++){ 233 display.drawTriangle(93, 59, 66 + (j*3), 5 + (j*6), 120 - (j*3), 5 + (j*6), WHITE); 234 } 235 236 // read the sensor 5 times and find the sum of the values 237 for (int j = 0; j < 5; j++){ 238 int sensor_value = analogRead(A0); 239 media += sensor_value; 240 } 241 242 Serial.println(media); // print media to serial monitor 243 244 // find the mean of the measured values, and round the result, to diminish the effect of current oscillations 245 media = media / 5; 246 media = round(media); 247 248 // print mean to serial monitor 249 Serial.print("Media:"); 250 Serial.println(media); 251 252 // display the value of the UV index, risk of harm from unprotected sun exposure, for the average adult , and fill the graph, according to the according to the media (rounded mean value of the sensor's readings) 253 if (media > 5){ 254 if (media < 69){ 255 display.fillTriangle(93,59,90,53,96,53, WHITE); // fill the graph 256 cursor0(); // set cursor to standardised position 257 display.println("<1"); // UV index 258 cursor1(); // set cursor to standardised position 259 display.println("Baixo"); // risk of harm 260 } 261 else if(media <= 121){ 262 display.fillTriangle(93,59,90,53,96,53, WHITE); // fill the graph 263 cursor0(); // set cursor to standardised position 264 display.println("1"); // UV index 265 cursor1(); // set cursor to standardised position 266 display.println("Baixo"); // risk of harm 267 } 268 else if(media <= 178){ 269 display.fillTriangle(93,59,87,47,99,47, WHITE); // fill the graph 270 cursor0(); // set cursor to standardised position 271 display.println("2"); // UV index 272 cursor1(); // set cursor to standardised position 273 display.println("Baixo"); // risk of harm 274 } 275 else if(media <= 211){ 276 display.fillTriangle(93,59,84,41,102,41, WHITE); // fill the graph 277 cursor0(); // set cursor to standardised position 278 display.println("3"); // UV index 279 cursor2(); // set cursor to standardised position 280 display.println("Moderado"); // risk of harm 281 } 282 else if(media <= 237){ 283 display.fillTriangle(93,59,81,35,105,35, WHITE); // fill the graph 284 cursor0(); // set cursor to standardised position 285 display.println("4"); // UV index 286 cursor2(); // set cursor to standardised position 287 display.println("Moderado"); // risk of harm 288 } 289 else if(media <= 256){ 290 display.fillTriangle(93,59,78,29,108,29, WHITE); // fill the graph 291 cursor0(); // set cursor to standardised position 292 display.println("5"); // UV index 293 cursor2(); // set cursor to standardised position 294 display.println("Moderado"); // risk of harm 295 } 296 else if(media <= 273){ 297 display.fillTriangle(93,59,75,23,111,23, WHITE); // fill the graph 298 cursor0(); // set cursor to standardised position 299 display.println("6"); // UV index 300 cursor1(); // set cursor to standardised position 301 display.println("Alto"); // risk of harm 302 } 303 else if(media <= 290){ 304 display.fillTriangle(93,59,72,17,114,17, WHITE); // fill the graph 305 cursor0(); // set cursor to standardised position 306 display.println("7"); // UV index 307 cursor1(); // set cursor to standardised position 308 display.println("Alto"); // risk of harm 309 } 310 else if(media <= 311){ 311 display.fillTriangle(93,59,69,11,117,11, WHITE); // fill the graph 312 cursor0(); // set cursor to standardised position 313 display.println("8"); // UV index 314 cursor2(); // set cursor to standardised position 315 display.println("Muito Alto"); // risk of harm 316 } 317 else if(media > 311){ 318 display.fillTriangle(93,59,66,5,120,5, WHITE); // fill the graph 319 cursor0(); // set cursor to standardised position 320 display.println("9+"); // UV index 321 cursor2(); // set cursor to standardised position 322 display.println("Muito Alto"); // risk of harm 323 } 324 } 325 else{ 326 cursor0(); // set cursor to standardised position 327 display.println("0"); // UV index 328 cursor1(); // set cursor to standardised position 329 display.println("Baixo"); // risk of harm 330 } 331 display.display(); // display the drawn info 332} 333 334/* 335 Mode 2 336 Mode 2 presents the user with a url (reduced with tinyurl.com) that they can use to access a google drive folder. 337 This folder contains manuals and guides, explaining exactly how to use and maintain the UV meter. It also contains a project log and tutorial, in portuguese, 338 along with other informations about the device, and a link to access the arduino hub project tutorial (in english). There is also a photo gallery. 339 */ 340void mode_2(){ 341 display.clearDisplay(); // clear display 342 343 // change the text size and cursor position 344 display.setTextSize(2); 345 display.setCursor(30,0); 346 display.println("Acesse"); // print text 347 348 // change the text size and cursor position 349 display.setTextSize(1); 350 display.setCursor(14,32); 351 display.drawRoundRect(9, 28, 110, 16, 3, WHITE); // draw rectangle to contain the url 352 display.print("tinyurl.com/senuv"); // print text 353 display.display(); // display the drawn info 354 355} 356 357// standardised cursor position for printing the UV index 358void cursor0(){ 359 display.setCursor(10,15); 360 display.setTextSize(4); 361} 362 363// standardised cursor position for printingthe risk of harm 364void cursor1(){ 365 display.setCursor(5,50); 366 display.setTextSize(1); 367} 368 369// other standardised cursor position for printing the risk of harm 370void cursor2(){ 371 display.setCursor(0,50); 372 display.setTextSize(1); 373} 374
Downloadable files
Schematics for the circuit
Understand how the circuit is organized and assembled
Schematics for the circuit
Documentation
Printed Circuit Board Fabrication file (Gerber)
Printed Circuit Board Fabrication file (Gerber)
Printed Circuit Board Fabrication file (Gerber)
PCB Easy EDA type file
PCB Easy EDA type file
3D printed enclosure
Custom-designed 3D-printed enclosure that fits all the components and the PCB nicely.
3D printed enclosure
Comments
Only logged in users can leave comments
d4visl
a year ago
Hi Rahul, I sent you an email, but you can also contact me at davisallesleite04@gmail.com
itzrahul14
a year ago
Hey, Rahul here, Is there any way I can contact you regarding this project? Please do reach out. maid id- rahuldgowda11@gmail.com, rahuldgowda07@gmail.com