At this point what is needed is a timer to control the lights and also a switch used for safety and to start the operations.
The timer
The original UV-C strip light used is powered by a USB 5V charger, in the end a timer is needed to power the lights for a specific amount of time (to be determined, see Tests section).
A possible candidate is actually the UV Box timer designed time ago but it is probably overshooting for this application.
A possible candidate is actually the UV Box timer designed time ago but it is probably overshooting for this application.
To simplify and assuming the time will be fixed rather than dynamic (i.e. no need to change constantly the exposure) a timer can be based on a Trinket that control a relay and something to indicate how long the lamps are working like a LED strip, maybe using one with RGB LED (WS2812B LED strip)
Here the shopping list for the electronic :
- Trinket 5V
- Relay
- WS2812B LED strip (Neopixel Ring or Bar)
- Power supply
Since the strip is powered by the USB 5V would be possible to control it via a transistor rather than a relay but in case more than one strip is needed, the circuit could need to use 12V UV-C LED strips and in this case the relay is better, is a better approach, more generic.
It happens that just recently I did work on a project using a 5V Trinket (Rotary Dial) and I did prepare a PCB for it, PCB that can be fully converted to become a timer.
Schematic
Let see the schematic, directly copied from the Rotary Dial project.
If compared with the Rotary Dial schematic, some components are missing.
We will need only one switch as input, the power supply (12V) will be routed to one relay contact and only the Neopixel will be used as mean to communicate the status.
With some jumper and small modifications will be possible to use the PCB of the Rotary Dial project, much better than use a breadboard and a point to point building.
We will need only one switch as input, the power supply (12V) will be routed to one relay contact and only the Neopixel will be used as mean to communicate the status.
With some jumper and small modifications will be possible to use the PCB of the Rotary Dial project, much better than use a breadboard and a point to point building.
Also the Rotary Dial case will be re-used for the project, only the top of the case will be changed in order to accommodate the Neopixel ring.
The final arrangement
And here the final arrangement for the basic UV sterilizer to be tested.
I did change the single line 5V UV LEDs with two 12V lines UV LED and powered everything with a 12V 2A wall transformer.
I screwed the controller to one side and for fun I did find an interesting 3D project to host the Adafruit Neopixel 16 LED Ring : https://www.thingiverse.com/thing:1183672
Then I used a micro-switch to control the lights and timer.
In this way the usage is really simple and intuitive.
In this way the usage is really simple and intuitive.
Is enough to open the box, put whatever you want to sterilize and close it.
The timer (15 minutes for now) starts as well as the UV LEDs.
The LEDs in the beacon are all red and every minute one change blue.
When all the ring is blue the 15 minutes are terminated and the ring become green indicating the end of the cycle, turning off the UV LEDs.
The timer (15 minutes for now) starts as well as the UV LEDs.
The LEDs in the beacon are all red and every minute one change blue.
When all the ring is blue the 15 minutes are terminated and the ring become green indicating the end of the cycle, turning off the UV LEDs.
When the door is opened the Ring LED are turned off.
Of course if during the cycle the door is opened, the UV LEDs are turned off as well as the Ring LEDs.
So let see some pictures of the ready for test UV Box.
The circuit mounted on the mailbox |
The double strip of UV LEDs lit |
A closed look to the circuit |
The micro-switch |
The Ring LED enclosure. The upper part is covered (inside) on tin foil to better reflect the light. The effect is amazing ! |
The code
The code is quite trivial, based on a state machine.
Here it is :
Here it is :
// UV sterilizer by TheFwGuy - July 2020
// The program use a Neopixel strip as feedback about a timer for activating UV lights
//
// A switch is hooked to the door.
// When the door is closed, the switch close contacts (thus reading zero)
// When the door is open the switch is open (thus reading Vcc)
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h> // Required for 16 MHz Adafruit Trinket
#endif
// ----- Defines
#define LED_PIN 1 // Used for Neopixel
#define SW1 0 // Door switch (marked on PCB as Pulse) - the input is pulled up
#define SW2 2 // N/A (marked on PCB as Signalling)
#define RELAY 4 // Relay
#define UV_START 0
#define UV_IDLE 1
#define UV_TIMING 2
#define UV_END 3
#define LED_COUNT 16
//#define END_EXPOSURE_TIME 1200000 // Time for exposure - ~20 minutes
#define END_EXPOSURE_TIME 900000 // Time for exposure - ~15 minutes
//#define END_EXPOSURE_TIME 60000 // Time for exposure - ~1 minute
#define CHANGE_PIXEL_TIME 60000 // Time for change pixel - ~1 minute
//#define CHANGE_PIXEL_TIME 3750 // Time for change pixel - ~3.75 second
// ------ Global variables
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
// Argument 1 = Number of pixels in NeoPixel strip
// Argument 2 = Arduino pin number (most are valid)
// Argument 3 = Pixel type flags, add together as needed:
// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products)
// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
char uv_status; // State machine
int count_up;
unsigned long currentMillis = 0; // stores the value of millis() in each iteration of loop()
unsigned long digitTimerMillis = 0; // Main timer
unsigned long changeCountMillis = 0; // Change color pixel timer
// setup() function -- runs once at startup --------------------------------
void setup() {
// These lines are specifically to support the Adafruit Trinket 5V 16 MHz.
// Any other board, you can remove this part (but no harm leaving it):
#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000)
clock_prescale_set(clock_div_1);
#endif
// END of Trinket-specific code.
uv_status = UV_START;
// Set input pins for rotary dial
pinMode(SW1,INPUT);
pinMode(SW2,INPUT);
pinMode(RELAY, OUTPUT);
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show(); // Turn OFF all pixels ASAP
strip.setBrightness(250); // Set BRIGHTNESS to about 1/5 (max = 255)
// Test strip
colorWipe(strip.Color( 0, 255, 0), 80); // Green
colorWipe(strip.Color( 255, 0, 0), 80); // Green
colorWipe(strip.Color( 0, 0, 255), 80); // Blue
}
// loop() function -- runs repeatedly as long as board is on ---------------
void loop() {
currentMillis = millis(); // Update time base
uv_state_machine();
}
void uv_state_machine () {
switch (uv_status) {
case UV_START:
default:
colorWipe(strip.Color( 0, 0, 0), 0); // Turn off everything
count_up = 1;
uv_status = UV_IDLE;
break;
case UV_IDLE:
// If reading switch is low, it means the door is closed
if (!readSwitch(SW1) ) {
colorWipe(strip.Color(255, 0, 0), 5); // Red
// Set timer
digitTimerMillis = currentMillis; // Initialize main timer
changeCountMillis = currentMillis; // Initialize change pixel timer
// Turn on light
digitalWrite(RELAY, HIGH);
uv_status = UV_TIMING;
}
break;
case UV_TIMING:
// If reading switch is close, it means the door is closed - stop if goes open
if (readSwitch(SW1) ) {
// Door open ! Turn off everything
digitalWrite(RELAY, LOW);
uv_status = UV_START;
} else {
// Door close - timer management
if (currentMillis - digitTimerMillis > END_EXPOSURE_TIME)
{
// Door open ! Turn off everything
digitalWrite(RELAY, LOW);
colorWipe(strip.Color( 0, 255, 0), 10); // Green
uv_status = UV_END;
} else {
// Main timer is running
if (count_up < LED_COUNT)
{
if (currentMillis - changeCountMillis > CHANGE_PIXEL_TIME)
{
changeCountMillis = currentMillis; // Re-Initialize change pixel timer
colorNumber(strip.Color( 0, 0, 255), 0, count_up);
count_up++;
}
}
}
}
break;
case UV_END:
// Wait for the switch to go open
if (readSwitch(SW1) ) {
uv_status = UV_START;
}
break;
}
}
// The function read a button with some debouncing - return true if the pin goes high.
// returns false otherwise
boolean readSwitch(int pin)
{
if(digitalRead(pin))
{
delay(1);
if(digitalRead(pin))
{
return true;
}
}
return false;
}
// Some functions of our own for creating animated effects -----------------
// Fill strip pixels one after another with a color. Strip is NOT cleared
// first; anything there will be covered pixel by pixel. Pass in color
// (as a single 'packed' 32-bit value, which you can get by calling
// strip.Color(red, green, blue) as shown in the loop() function above),
// and a delay time (in milliseconds) between pixels.
void colorWipe(uint32_t color, int wait) {
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
strip.show(); // Update strip to match
delay(wait); // Pause for a moment
}
}
// Display a number (from 0 to 8)
void colorNumber(uint32_t color, int wait, char number) {
for(int i=0; i<number; i++) { // For each pixel in strip...
strip.setPixelColor(i, color); // Set pixel's color (in RAM)
strip.show(); // Update strip to match
delay(wait); // Pause for a moment
}
}
// Theater-marquee-style chasing lights. Pass in a color (32-bit value,
// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms)
// between frames.
void theaterChase(uint32_t color, int wait) {
for(int a=0; a<10; a++) { // Repeat 10 times...
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
strip.clear(); // Set all pixels in RAM to 0 (off)
// 'c' counts up from 'b' to end of strip in steps of 3...
for(int c=b; c<strip.numPixels(); c += 3) {
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
}
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
}
}
}
// Rainbow cycle along whole strip. Pass delay time (in ms) between frames.
void rainbow(int wait) {
// Hue of first pixel runs 5 complete loops through the color wheel.
// Color wheel has a range of 65536 but it's OK if we roll over, so
// just count from 0 to 5*65536. Adding 256 to firstPixelHue each time
// means we'll make 5*65536/256 = 1280 passes through this outer loop:
for(long firstPixelHue = 0; firstPixelHue < 5*65536; firstPixelHue += 256) {
for(int i=0; i<strip.numPixels(); i++) { // For each pixel in strip...
// Offset pixel hue by an amount to make one full revolution of the
// color wheel (range of 65536) along the length of the strip
// (strip.numPixels() steps):
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
// strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or
// optionally add saturation and value (brightness) (each 0 to 255).
// Here we're using just the single-argument hue variant. The result
// is passed through strip.gamma32() to provide 'truer' colors
// before assigning to each pixel:
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
}
}
// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames.
void theaterChaseRainbow(int wait) {
int firstPixelHue = 0; // First pixel starts at red (hue 0)
for(int a=0; a<30; a++) { // Repeat 30 times...
for(int b=0; b<3; b++) { // 'b' counts from 0 to 2...
strip.clear(); // Set all pixels in RAM to 0 (off)
// 'c' counts up from 'b' to end of strip in increments of 3...
for(int c=b; c<strip.numPixels(); c += 3) {
// hue of pixel 'c' is offset by an amount to make one full
// revolution of the color wheel (range 65536) along the length
// of the strip (strip.numPixels() steps):
int hue = firstPixelHue + c * 65536L / strip.numPixels();
uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB
strip.setPixelColor(c, color); // Set pixel 'c' to value 'color'
}
strip.show(); // Update strip with new contents
delay(wait); // Pause for a moment
firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames
}
}
}
No comments:
Post a Comment