Timer for horticulture

Timer for horticulture

I love growing plants. Besides loving plants, I see this as a challenge to do…more.

Like growing coconut trees from seed, peppers. Usually I start planting them during winter, so the plant will be already grown during its first summertime.

Growing oriental and tropical plants during harsh winters in Romania is quite a challenge. Plants need warmth, which is available, but they also need light. Lots of light, which in turn is not available indoors without extra lighting.

A few years ago I started looking into this matter and I came up with a solution which solves the matter of having strong plants that will not die during winter from the lack of light.

 

1. The light itself.

A plant looks green because green is the color it reflects from the whole light spectrum. The plant doesn’t need green light, it needs instead almost all colors, from infrared to ultraviolet, to make photosynthesis and generate nutrients.

I have conceived a small Led lighting plate, made from half of an A4 board.

Schematic:

The Leds are all 5050, with 3 leds on each chip.

PCB:

The PCB should be populated like this:

 

WW=Ward White

CW=Cold White

R=Red

O=Orange

B=Blue

UV=Ultra Violet

All the leds are easily found on Ebay, quite cheap.

 

Next, a timer would come in handy in order to automate the lighting process.

 

Schematic:

1. – Power stage

2. – Micro-controller stage

3. – Keyboard and ISP stage

4. – Power Mosfet stage

5. – Display stage

6. – PCB

As in my other timers designs, the core is an Atmel micro-controller, in this particular case an ATMega8, because I needed an analog input to measure the light intensity and more flash, both unavailable on the small AtTiny2313.

 

How it works:

When started, the device shows the light intensity (0-100) and it starts assessing the conditions to start or stop the timer.  After a few seconds, in goes into power saving mode, lighting just the 3rd decimal point every second. The power saving mode is necessary to let the 7805 cool down because of the direct drive of the 7-segments display.

Every 15 seconds, it display for 2 seconds:

– the light intensity, on the form L.XXX;

– the threshold that was set up, on the form P.XXX;

– the remaining time of lighting (the current value of the timer), in the form HH.MM.

Yes, this timer does not show seconds anymore, it shows hours and minutes.

The left and right keys are increasing and decreasing respectively the time set for the timer or the threshold.

The GO key circles between the current light intensity (0-100, where 0 is dark and 100 is fully lit), the threshold and the time to set for the timer.

LEFT and RIGHT keys pressed simultaneously will write in EEPROM both the timer countdown value and the threshold value. set up by the user. At the next power-up, these will be the values used by default for countdown and threshold. After writing these values in EEPROM, the device shows “8888” for 2 seconds.

Any key press resets the timer and shuts it down, if in operation.

Left alone, the device performs the following:

– When the sun sets and the light goes beyond the set threshold, it waits for one minute and starts the timer. The timer will count backwards, keeping the lighting plate lit for the specified hours and minutes.

– When the timer expires, the light shuts off. The timer is programmable between 0 and 12 hours, with 10 minutes increments. The timer will not start again without the condition of the rising sun: the light must go over the threshold for 10 minutes, to reset the device and allow it to begin a new cycle. The 10 minutes are necessary in order not to reset the device if, for example, one is lighting the room for a period less than 10 minutes.

– During countdown, the timer can be shut off by pressing any key. This will also make the device reset and begin lighting again if the light goes beyond the specified threshold.

 

The code is written in C and compiles under AVR Studio 4.

The fuses for AtMega8 are set as follows:

 

RSTDISBL   - disabled 
WDTON      - disabled 
SPIEN      - enabled 
EESAVE     - disabled
BOOTSZ     - Boot flash size=128 words
BOOTRST    - disabled
CKOPT      - disabled
BODLEVEL   - brown-out detection at VCC=2.7V
BODEN      - enabled
SUT_CKSEL  - Internal RC Oscillator 8MHz; Start-up time: 14CK + 65 ms

 

AVR Studio 4 C code:

#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include "config.h"
#include "adc.h"


uint16_t EEMEM Etime=240;    //240 default
uint16_t Def_time=240;
uint8_t Programmedtime=240;
uint8_t EEMEM EThreshold=50;
uint8_t Def_Threshold=50;

//global variables for isr()
uint16_t time, digit, decimal;
uint8_t wdisplay=0;
uint8_t t;
uint8_t LedOn=0;
//uint8_t ThresholdSetup=0;
uint8_t UVValue=100;
uint8_t Threshold=0;
uint8_t light,cancount,counted, counting;

//keys definition
#define GO        5
#define RIGHT     3
#define LEFT     4
#define KPIN  PINB
uint8_t keypressed=1;        //a key was pressed
uint16_t t1=0,t2=0,t3=0,t4=0,t5=0;
uint8_t Powersave=0;

#define SEG_a 0x02
#define SEG_b 0x01
#define SEG_c 0x10
#define SEG_d 0x40
#define SEG_e 0x20
#define SEG_f 0x04
#define SEG_g 0x08
#define SEG_dot 0x80


#define MuxPort PORTB
#define MuxDigit0 DDB0
#define MuxDigit1 DDB1
#define MuxDigit2 DDB6
#define MuxDigit3 DDB7
#define DataPort PORTD



uint16_t digit_addressed=0;
uint8_t d[8];
uint16_t pulseinterval;
uint16_t maxpulseinterval=512;
uint8_t pulsing=0;


//character definition
//a<->f and b<->g are inverted software.

unsigned char seg[]=
{
    //negative logic
    (SEG_g|SEG_dot),                                         // 0
    (SEG_a|SEG_d|SEG_e|SEG_f|SEG_g|SEG_dot),                // 1
    (SEG_c|SEG_f|SEG_dot),                                     // 2
    (SEG_e|SEG_f|SEG_dot),                                     // 3
    (SEG_a|SEG_d|SEG_e|SEG_dot),                             // 4
    (SEG_b|SEG_e)|SEG_dot,                                     // 5
    (SEG_b|SEG_dot),                                         // 6
    (SEG_d|SEG_e|SEG_f|SEG_g|SEG_dot),                        // 7
    (SEG_dot),                                                 // 8
    (SEG_e|SEG_dot),                                         // 9
    (SEG_a|SEG_b|SEG_c|SEG_d|SEG_e|SEG_f|SEG_g),             // NULL DISPLAY FOR POWER SAVE WITHOUT DOT
    (SEG_a|SEG_b|SEG_c|SEG_g),                                // L
    (SEG_c|SEG_d),                                             // P
    (SEG_a|SEG_b|SEG_c|SEG_d|SEG_e|SEG_f|SEG_g|SEG_dot)     // NULL DISPLAY FOR POWER SAVE WITH DOT
};

void init(void)
{
//timer 0 setup
    TCNT0 = 0x00 ;
    TCCR0 = 0x03; //prescaler of 64 0x03
    TIMSK = 0x01; //overflow irq req'd
//timer1 setup
    //we need PB4 (OC1B) as PWM but PB3 (PC1A) as standard output port
    TCCR1A = 
    0 << COM1A1 | /* normal port operation, PWM disabled */
    0 << COM1A0 |
    1 << COM1B1 | /* not inverted */
    0 << COM1B0 |
    1 << WGM10  | /* fast pwm 8 bit*/
    0 << WGM11  ; /* fast pwm 8 bit*/

    TCCR1B = 
    1 << WGM12 |
    0 << WGM13 | /*fast pwm non inverting max FF*/
    1 << CS10  | /* prescaler /8 = 250 Hz PWM */
    1 << CS11  |
    0 << CS12  ;

    ADCSRA |= _BV( ADEN ) | _BV(ADPS0) | _BV(ADPS1) | _BV(ADPS2);
//    GIMSK = 0b00100000;    // turns on pin change interrupts
//    PCMSK = 0b11100000;    // turn on interrupts on pins PB0, PB1, 
    sei();
    DDRC  =0x00;                                    //PORTC defined as input
    PORTC=0x01;                                        //pull resistors up on port C
    DDRB  =_BV(0)|_BV(1)|_BV(2)|_BV(6)|_BV(7);        //bits 0-2 and 6-7 of PORTB are defined as outputs
    PORTB =_BV(0)|_BV(1)|_BV(2)|_BV(6)|_BV(7);        //bits 3-5 of PORTB are defined as inputs
    DDRB  = DDRB& ~_BV(2);                            //set PB2 as input, to clear residual PWM

    PORTD=0xff;
    DDRD =0xff;
    pulsing=0;                //pulsing
    pulseinterval=0;
    time= eeprom_read_word(&Etime);
    if (time==0xFFFF)        //not  EEPROM
    {
        time=Def_time;
        eeprom_write_word(&Etime, Def_time);
    }

    Threshold= eeprom_read_byte(&EThreshold);
    if (Threshold==0xFF)        //not programmed EEPROM
    {
        Threshold=Def_Threshold;
        eeprom_write_byte(&EThreshold, Def_Threshold);
    }
    OCR1B=light*255/100;                                    //init value on PWM at zero

}


//interrupt routine for character display
ISR(TIMER0_OVF_vect)
{
    pulseinterval++;
    if (pulseinterval>maxpulseinterval) pulseinterval=0;
    if ((pulsing==1) && (pulseinterval > maxpulseinterval/2))
    {
        MuxPort=(1<<DDB0) | (1<<DDB1)| (1<<DDB6)| (1<<DDB7);
    }
    else
    {    
        PORTD = ~(d[digit_addressed]);

        switch (digit_addressed)
        {
            case 0: 
            {
                MuxPort=(0<<MuxDigit0) | (1<<MuxDigit1)| (1<<MuxDigit2)| (1<<MuxDigit3); 
            break;
            }     
            case 1: 
            {
                MuxPort=(1<<MuxDigit0) | (0<<MuxDigit1)| (1<<MuxDigit2)| (1<<MuxDigit3); 
                if ((wdisplay==2) && (Powersave!=1))
                {
                    PORTD|=_BV(7);
                }         
                else
                {
                PORTD=PORTD&~_BV(7);
                }//set   bit 7 port d (dot point) if we display time
            break;
            }
            case 2: 
            {
                MuxPort=(1<<MuxDigit0) | (1<<MuxDigit1)| (1<<MuxDigit2)| (0<<MuxDigit3); 
            break;
            }    
            default:
            {
                MuxPort=(1<<MuxDigit0) | (1<<MuxDigit1)| (0<<MuxDigit2)| (1<<MuxDigit3); 
            }    
        }
        digit_addressed++;
        if(digit_addressed>=4) digit_addressed=0;
    }
}


void display(void)
{
    uint8_t hr, min;
    if (Powersave!=1)
    {
        if (wdisplay==0)    //light level
        {
            if (digit==100)
            {
                d[4]=seg[11];
                d[5]=1;
                d[6]=0;
                d[7]=0;
            }
            else    
            {
                d[4]=seg[11];
                d[5]=0;
                d[6]=digit/10;
                d[7]=digit- d[6]*10;
            }
            d[0]=seg[11];
            if (digit==100) d[1]= seg[1];    else d[1]=seg[13];
            if (digit>=10)  d[2]= seg[d[6]]; else d[2]=seg[13];
            d[3]= seg[d[7]];
        }
        else
        if (wdisplay==1)    //threshold
        {
            if (digit==100)
            {
                d[4]=seg[12];
                d[5]=1;
                d[6]=0;
                d[7]=0;
            }
            else    
            {
                d[4]=seg[12];
                d[5]=0;
                d[6]=digit/10;
                d[7]=digit- d[6]*10;
            }
            d[0]=seg[12];
            if (digit==100) d[1]= seg[1];    else d[1]=seg[13];
            if (digit>=10)  d[2]= seg[d[6]]; else d[2]=seg[13];
            d[3]= seg[d[7]];
        }
        else
        if (wdisplay==2)    //time
        {
            hr=digit/60;
            min=digit-hr*60;
        
            //computing digits
            d[4]=hr/10;
            d[5]=hr-d[4]*10;
            d[6]=min/10;
            d[7]=min-d[6]*10;
            d[0]= seg[d[4]];
            d[1]= seg[d[5]];
            d[2]= seg[d[6]];
            d[3]= seg[d[7]];
        }
    }
    else
    {
        d[4]=counting;
        d[5]=counted;
        d[6]=cancount;
        d[0]=seg[d[4]];
        d[1]=seg[d[5]];
        d[2]=seg[d[6]];
        d[0]= seg[13];    //comment here for debugging
        d[1]= seg[13];  //comment here for debugging
        d[2]= seg[10];  //comment here for debugging
        d[3]= seg[13];
    }
}




void readkeysT1(void)
{
//LEFT AND RIGHT keys

//LEFT key
    if (bit_is_clear(KPIN, LEFT) && bit_is_clear(KPIN, RIGHT))
    {
        eeprom_write_word(&Etime, time);
        eeprom_write_byte(&EThreshold, Threshold);
        keypressed=1;
        d[0]= seg[8];
        d[1]= seg[8];
        d[2]= seg[8];
        d[3]= seg[8];
        _delay_ms(2000);
    }
    else
    if bit_is_clear(KPIN, LEFT) 
    {
        pulsing=0;
        counting=0;        //we're not counting
        counted=0;        //never did
        cancount=0;    //cannot count yet
        keypressed=1;
        _delay_ms(50);
        if (wdisplay==0) wdisplay=1;
        if (wdisplay==2)
        {
            time-=10;
            if (time>5999) time=0;
        }
        else
        {
            Threshold--;
            if (Threshold==0xFF) Threshold=0;
        }
    }
    else
//RIGHT key
    if bit_is_clear(KPIN, RIGHT) 
    {
        pulsing=0;
        counting=0;        //we're not counting
        counted=0;        //never did
        cancount=0;    //cannot count yet
        keypressed=1;
        _delay_ms(50);
        if (wdisplay==0) wdisplay=1;
        if (wdisplay==2)
        {
            time+=10;
            if (time>720) time=720;
        }
        else
        {
            Threshold++;
            if (Threshold>100) Threshold=100;
        }
    }
    else
        if bit_is_clear(KPIN, GO) 
    {
        pulsing=0;
        keypressed=1;
        Powersave=0;
        wdisplay++;
        counting=0;        //we're not counting
        counted=0;        //never did
        cancount=0;    //cannot count yet
        if (wdisplay==3) wdisplay=0;
        _delay_ms(100);
        loop_until_bit_is_set(KPIN,GO);
    }
    else    keypressed=0;
}




void testfordisplaysleep(void)
{
    if (keypressed==0)    //if there is no key pressed
    {
        t1++;            
        if (t1==100)    //wait ~10 seconds
        {
            t1=0;        
            Powersave=1;//and put display to sleep
            pulsing=1;
        }
    }
    else                //on any key press 
    {    
        t1=0;
        t2=0;
        Powersave=0;    //wake up display

    }
    if ((t2>0) || (Powersave==1))    //display values during powersave
    {
        t2++;
        if ((t2>200) && (t2<235))
        {
            Powersave=0;
            wdisplay=0;
            pulsing=0;
        }
        else
        if ((t2>240) && (t2<275))
        {
            Powersave=0;
            wdisplay=1;
            pulsing=0;
        }
        else
        if ((t2>280) && (t2<315))
        {
            Powersave=0;
            wdisplay=2;
            pulsing=0;
        }
        else
        if (t2>315)
        {
            Powersave=1;
            t2=0;
            pulsing=1;
        }
        else
        {
            Powersave=1;
            pulsing=1;
        }
    }
}

void ShowLight()
{

/*
Trebuie evitate aprinderile accidentale de lumina, astfel:


Daca lumina scade sub prag timp de 1 minut, aprindem lumina artificiala si incepem numaratoarea inversa
La sfarsitul numaratorii inverse, inchidem lumina pana se ridica iar nivelul de lumina peste prag, timp de 1 minut.

Daca, in timpul numaratorii inverse, apare lumina peste prag, numaram mai departe.fara sa ne intereseze aspectul

*/
        if (counted==0)                            //if there was no count during the day
        {
            if (light<Threshold)                //if light goes under the threshold
            {
                if (counting==0)
                {
                    t3++;                            //start counting one minute
                    if (t3==800)                    //if one minute passes, i.e. 800 cycles
                    {
                        cancount=1;                    //we then can count
                        t3=0;                        //we reset the 1 minute delay
                        Programmedtime=time;        //setting count back to the set value
                    }
                }
            }
            else                                //still waiting for 1 minute to pass
            {
                if (counting==0)                //if light goes over the threshold for 10 minutes (new daylight) and we're not counting
                {
                    t5++;                                //start counting 10 minutes
                    if (t5==8000)                        //if 10 minutes pass, i.e. 8000 cycles
                    {
                        counted=0;                        //we reset everything
                        cancount=0;
                        counting=0;
                        t5=0;                            //including the 10 minutes delay
                        t3=0;
                    }
                }
            }
        }
        else                                        //if there was a count during the day
        {                                            //we have to reset the count on the next day
            if (light>Threshold)                     //if light goes over the threshold for 10 minutes (new daylight)
            {
                t5++;                                //start counting one minute
                if (t5==8000)                        //if 10 minutes pass, i.e. 8000 cycles
                {
                    counted=0;                        //we reset everything
                    cancount=0;
                    counting=0;
                    t5=0;                            //including the 10 minutes delay
                }
            }

        }


}


void count()
{

            if (cancount==0)             //if we can't count because we have either counted or because the light didn't diminish under the threshold
            {
                counting=0;                //not counting
                DDRB  = DDRB& ~_BV(2);    //turn off the lights
            }
            else                        //if we can count
            {
                counting=1;                //we're counting
                t4++;
                if (t4==800)            //if 1 minute passes (800 cycles)
                {
                    Programmedtime--;    //we then can count backwards
                    t4=0;                //we reset the 1 minute delay
                }
                if (Programmedtime==0)    //if counting ends
                {
                    cancount=0;            //can't count anymore
                    counted=1;            //we have just finished counting
                    counting=0;            //not counting anymore
                    Programmedtime=time;//reset counter
                }
                DDRB |=_BV(2);            //turn on the lighta
//                OCR1B=(100-light)*255/100; //de calculat iluminarea cu 105 sub prag si 100% la intuneric
                OCR1B=255;                
            }
}

int main()
 
{
    init();
    _delay_ms(10);
    LedOn=0;
    PORTD=_BV(7);    // keep DP lit
    t1=0;
    keypressed=1;
    counting=0;        //we're not counting
    counted=0;        //never did
    cancount=0;    //cannot count yet

    while(1)
    {
        readkeysT1();                            //read key
        display();                                //display number
        _delay_ms(50);                            //wait a little bit
        testfordisplaysleep();                    //put the display to sleep if needed
        if (wdisplay==0) digit=light;            //display whatever values are needed
        else
        if (wdisplay==1) digit=Threshold;
        else
        if (wdisplay==2)
        {
            if (counting==0)     
                digit=time;
            else
            {
                digit=Programmedtime;
            }
        }
        light=GetLight();                        //read light level
        ShowLight();                            //lights on if needed
        count();
    }

}
 

The ADC header file:

#ifndef ADC_H_
#define ADC_H_

#include <stdint.h>

/**
 * \name ADC channels for analog inputs
 * @{
 */
#define LIGHT_PIN 0
///@}

uint8_t GetADC( uint8_t channel );
uint8_t GetLight();

#endif 


The ADC C file:



#include "adc.h"
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>



uint8_t GetADC( uint8_t channel )
{

    uint8_t i, lum[100], lumend,a;
    ADMUX = channel | _BV( REFS0 ) | _BV(ADLAR); //8bit + AVCC
    lumend=0;

    for (i=0;i<100;i++)
    {
    //start conversion.
        ADCSRA |= _BV(ADSC);

         //wait for the 1st result
        loop_until_bit_is_clear( ADCSRA, ADSC );
        lum[i]= ADCH; //left adjusted = 8 bit
        lumend=(lumend*(i+1)+lum[i])/(i+2);
    }

    a=102-lumend*25/58;
    if (a>200) a=0;
    if (a>100) a=100;
    return a;
}

uint8_t GetLight()
{
    return GetADC( LIGHT_PIN );
}




If this is useful for you, please donate. I need to buy parts, make time to study and pay for the hosting. Thanks!

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

Prove you are human: *