Thursday, 25 April 2013

Working UV controller

Our UV controller is finally complete.
We didn't have any rotary encoders to hand so fudged it with a rotary potentiometer and some push buttons. If we were to make this again, we'd go for a slightly more sophisticated menu system and use a single rotary encoder with button built in. But this will do for now!




Here's the component layout. There are loads of extra connections for power and ground connections. There's nothing worse than soldering up a board and having to tack two or more wires together because there's nowhere on the board to add on another ground wire or power cable. This time, we made sure there are plenty!



Define CONF_WORD = 0x3f71
Define CLOCK_FREQUENCY = 4
AllDigital

Config PORTA = Input
Config PORTB = Output
Config PORTC = Output
Config PORTD = Output
Config PORTE = Output

Symbol white_led_pin = PORTD.1
Symbol uv_led_pin1 = PORTD.2
Symbol uv_led_pin2 = PORTD.3
Symbol uv_led_pin3 = PORTC.4
Symbol uv_led_pin4 = PORTC.5
Symbol uv_led_pin5 = PORTD.4
Symbol uv_led_pin6 = PORTD.5
Symbol uv_led_pin7 = PORTD.6
Symbol uv_led_pin8 = PORTB.4
Symbol speaker = PORTE.2

PORTB = 0x00
PORTD = 0x00

ADCON1 = 00001110b 'AN0 is analogue, all others digital
Define ADC_CLOCK = 3
Define ADC_SAMPLEUS = 50

Dim state As Byte
Dim lastmenu As Word
Dim menu As Word
Dim tmpw As Word
Dim tmp As Byte
Dim setting As Byte
Dim lastsetting As Byte
Dim uvled As Byte
Dim brightwhite As Byte
Dim brightuv As Byte
Dim buttonpressed As Byte
Dim timermins As Byte
Dim timersecs As Byte
Dim intrcnt As Byte
Dim seccount As Word
Dim redrawtimer As Byte

Dim led_counter As Byte

Define LCD_BITS = 4
Define LCD_DREG = PORTB
Define LCD_DBIT = 0
Define LCD_RSREG = PORTB
Define LCD_RSBIT = 5
Define LCD_RWREG = PORTB
Define LCD_RWBIT = 6
Define LCD_EREG = PORTB
Define LCD_EBIT = 7
Define LCD_READ_BUSY_FLAG = 0
Define LCD_DATAUS = 200
Define LCD_INITMS = 200

init:
     'allow everything to settle
     WaitMs 500

     Lcdinit 0 '0=no cursor
     Lcdcmdout LcdClear
     state = 0
     menu = 0
     lastmenu = 0
     setting = 0
     lastsetting = 0
     uvled = 0
     buttonpressed = 0
     intrcnt = 0
     redrawtimer = 0
     led_counter = 0
     
     Read 2, brightwhite
     Read 3, brightuv

     If brightwhite > 10 Then brightwhite = 10
     If brightuv > 10 Then brightuv = 10

     timermins = 1
     timersecs = 28

     Gosub resettimer

     'create a timer to fire every 1m/s
     '(we use this for the countdown AND to control the LEDs)
     
     T1CON = 0x00
     T1CON.T1OSCEN = 1
     T1CON.TMR1CS = 0
     T1CON.TMR1ON = 0
     Gosub preload_timer

     'enable global and peripheral interrupts
     INTCON.GIE = 1
     INTCON.PEIE = 1
     PIE1.TMR1IE = 1
     PIR1.TMR1IF = 0
     
     state = 0

loop:

     '-----------------------------------------------------------------------
     'read the value from RA0 to see if the user has changed the menu setting
     '-----------------------------------------------------------------------
     Adcin 0, menu
     If menu > lastmenu Then
          tmpw = menu - lastmenu
     Else
          tmpw = lastmenu - menu
     Endif

     If tmpw > 3 Then
          'dial has been moved more than the error in reading the A/C
          'so convert this to a setting value
          If menu >= 0 And menu < 340 Then
               'setting one:
               setting = 1
          Else
               If menu >= 340 And menu < 680 Then
                    'centre position: off
                    setting = 0
               Else
                    If menu >= 680 And menu <= 1024 Then
                         'setting two
                         setting = 2
                    Endif
               Endif
          Endif
     Endif

     '----------------------------------------------------
     'if the setting has changed, update the state machine
     '----------------------------------------------------
     If setting <> lastsetting Then

          'stop the timer
          T1CON.TMR1ON = 0
     
          'turn off any leds (they'll come back on again in a minute)
          Low white_led_pin 'turn off the white LED(s)
          Gosub turn_uv_off

          'something has changed: update the state as necessary
          Select Case setting
               Case 1 'start the uv leds again
               state = 1
               uvled = 1

               Case 0
               state = 0
               uvled = 0 'turn off the UV LEDs
               
               Case 2
               state = 2
               uvled = 0 'make sure uv LEDs are off

          EndSelect
     Endif

     lastsetting = setting
     lastmenu = menu


     '---------------------
     'decide what to do now
     '---------------------
     Select Case state
          Case 0
          'redraw the opening menu
          Gosub drawmenu1a
          state = 3 'wait for user to select a menu option

          Case 1
          'start flashing the UV LEDs, one row at a time
          Gosub resettimer
          Lcdcmdout LcdClear
          Lcdout "Time remaining:"
          Gosub drawtimer
          state = 5
          redrawtimer = 0
          'start timer1 interrupt
          Gosub preload_timer
          T1CON.TMR1ON = 1
          
          Case 2
          'show the white LEDs only
          Read 2, brightwhite
          If brightwhite > 10 Then brightwhite = 10
          Gosub drawmenu2
          Gosub turn_uv_off
          'start timer1 interrupt
          Gosub preload_timer
          T1CON.TMR1ON = 1
          state = 4 'wait for the user to select up/down

          Case 3
          'user can select between brightness and timer or hit select
          Gosub is_button_one_pressed
          If buttonpressed = 1 Then
               Gosub drawmenu1b
               state = 7
          Endif
          
          Gosub is_button_two_pressed
          If buttonpressed = 1 Then
               state = 8
               Gosub drawmenu4
          Endif

          Case 4
          'White LED is running
          'user can press up/down to select the white light brightness
          Gosub is_button_one_pressed
          If buttonpressed = 1 Then
               If brightwhite < 10 Then
                    brightwhite = brightwhite + 1
                    Gosub drawmenu2
               Endif
          Endif

          Gosub is_button_two_pressed
          If buttonpressed = 1 Then
               Low white_led_pin
               Write 2, brightwhite
               Gosub drawsaved
               For tmp = 1 To 10
                    WaitMs 200
               Next tmp
               Gosub drawmenu2
          Endif

          Gosub is_button_three_pressed
          If buttonpressed = 1 Then
               If brightwhite > 0 Then
                    brightwhite = brightwhite - 1
                    Gosub drawmenu2
               Endif
          Endif
                    

          Case 5
          'UV lights are running
          'user can press up/down to increase/decrease the CURRENT time by one sec
          Gosub is_button_one_pressed
          If buttonpressed = 1 Then
               Gosub increase_time_one_sec
               redrawtimer = 1
          Endif

          Gosub is_button_three_pressed
          If buttonpressed = 1 Then
               Gosub decrease_time_one_sec
               redrawtimer = 1
          Endif
          
          If redrawtimer = 1 Then
               redrawtimer = 0
               Gosub drawtimer
          Endif

          'check the timer hasn't hit zero
          If timermins = 0 And timersecs = 0 Then
               'stop the timer
               T1CON.TMR1ON = 0
               'draw zero zero
               Gosub drawtimer
               'time out, do nothing until menu selector moved
               Low white_led_pin
               Gosub turn_uv_off
               state = 6
          Endif
               
          Case 6
          'play a tune
          FreqOut speaker, 440, 200
          FreqOut speaker, 392, 200
          FreqOut speaker, 440, 200
          FreqOut speaker, 392, 200
          state = 13

          Case 7
          'user can select between brightness and timer or hit select
          Gosub is_button_three_pressed
          If buttonpressed = 1 Then
               Gosub drawmenu1a
               state = 3
          Endif
          Gosub is_button_two_pressed
          If buttonpressed = 1 Then
               Read 3, brightuv
               If brightuv > 10 Then brightuv = 10
               Gosub drawmenu3
               state = 9
          Endif

          Case 8
          'set the timer minutes
          Gosub is_button_three_pressed
          If buttonpressed = 1 Then
               state = 10
               Gosub drawmenu4
          Endif
          Gosub is_button_two_pressed
          If buttonpressed = 1 Then
               state = 11
               Gosub drawmenu4
          Endif

          Case 9
          'set the UV brightness
          'user can press up/down to select the UV light brightness
          Gosub is_button_one_pressed
          If buttonpressed = 1 Then
               If brightuv < 10 Then
                    brightuv = brightuv + 1
                    Gosub drawmenu3
               Endif
          Endif

          Gosub is_button_two_pressed
          If buttonpressed = 1 Then
               Gosub turn_uv_off
               Write 3, brightuv
               Gosub drawsaved
               For tmp = 1 To 10
                    WaitMs 200
               Next tmp
               Gosub drawmenu1a
               state = 3
          Endif

          Gosub is_button_three_pressed
          If buttonpressed = 1 Then
               If brightuv > 0 Then
                    brightuv = brightuv - 1
                    Gosub drawmenu3
               Endif
          Endif


          Case 10
          'set the UV timer seconds
          Gosub is_button_one_pressed
          If buttonpressed = 1 Then
               state = 8
               Gosub drawmenu4
          Endif
          Gosub is_button_two_pressed
          If buttonpressed = 1 Then
               state = 12
               Gosub drawmenu4
          Endif
          
          Case 11
          'use up/down to change the timer minutes
          Gosub is_button_one_pressed
          If buttonpressed = 1 Then
               Gosub increase_time_one_min
               Gosub drawmenu4
          Endif
          Gosub is_button_three_pressed
          If buttonpressed = 1 Then
               Gosub decrease_time_one_min
               Gosub drawmenu4
          Endif
          Gosub is_button_two_pressed
          If buttonpressed = 1 Then
               Write 10, timermins
               Write 11, timersecs
               state = 8
               Gosub drawmenu4
          Endif

          Case 12
          'use up/down to change the timer seconds
          Gosub is_button_one_pressed
          If buttonpressed = 1 Then
               Gosub increase_time_one_sec
               Gosub drawmenu4
          Endif
          Gosub is_button_three_pressed
          If buttonpressed = 1 Then
               Gosub decrease_time_one_sec
               Gosub drawmenu4
          Endif
          Gosub is_button_two_pressed
          If buttonpressed = 1 Then
               Write 10, timermins
               Write 11, timersecs
               state = 10
               Gosub drawmenu4
          Endif

          Case 13
          'do nothing: wait for the user to move the menu selector to something else

     EndSelect
Goto loop
End

drawmenu1a:
     Lcdcmdout LcdClear
     Lcdout " UV brightness "
     Lcdcmdout LcdLine2Home
     Lcdout " UV timer <"
Return

drawmenu1b:
     Lcdcmdout LcdClear
     Lcdout " UV brightness <"
     Lcdcmdout LcdLine2Home
     Lcdout " UV timer "
Return

drawmenu2:
     Lcdcmdout LcdClear
     Lcdout "Brightness: "
     If brightwhite < 100 Then Lcdout " "
     If brightwhite < 10 Then Lcdout " "
     Lcdout #brightwhite
Return

drawmenu3:
     Lcdcmdout LcdClear
     Lcdout "Brightness: "
     If brightuv < 100 Then Lcdout " "
     If brightuv < 10 Then Lcdout " "
     Lcdout #brightuv
Return

drawmenu4:
     Lcdcmdout LcdClear
     Lcdout " Minutes: "
     If timermins < 10 Then Lcdout " "
     Lcdout #timermins
     If state = 8 Then Lcdout " <"
     Lcdcmdout LcdLine2Home
     Lcdout " Seconds: "
     If timersecs < 10 Then Lcdout " "
     Lcdout #timersecs
     If state = 10 Then Lcdout " <"
Return

drawsaved:
     Lcdcmdout LcdClear
     Lcdout " Settings saved"
Return

resettimer:
     Read 10, timermins
     Read 11, timersecs

     If timermins = 255 Then timermins = 1
     If timersecs = 255 Then timersecs = 30

     If timermins = 0 And timersecs = 0 Then
          timermins = 1
          timersecs = 30
     Endif
Return

increase_time_one_sec:
     timersecs = timersecs + 1
     If timersecs > 59 Then
          timersecs = 0
          timermins = timermins + 1
     Endif
Return

decrease_time_one_sec:
     If timersecs > 0 Then
          timersecs = timersecs - 1
     Else
          If timermins > 0 Then
               timersecs = 59
               timermins = timermins - 1
          Else
               'timer is already at zero
               timersecs = 0
               timermins = 0
          Endif
     Endif
Return

increase_time_one_min:
     If timermins < 99 Then
          timermins = timermins + 1
     Endif
Return

decrease_time_one_min:
     If timermins > 0 Then
          timermins = timermins - 1
     Endif
Return

drawtimer:
     Lcdcmdout LcdLine2Home
     Lcdout " "
     If timermins < 10 Then Lcdout " "
     Lcdout #timermins
     Lcdout "m "
     If timersecs < 10 Then Lcdout " "
     Lcdout #timersecs
     Lcdout "sec"
Return

is_button_one_pressed:
     buttonpressed = 0
     If PORTA.1 = 1 Then
          'debounce the button press
          WaitMs 1
          If PORTA.1 = 1 Then
               While PORTA.1 = 1
                    'do nothing
               Wend
               buttonpressed = 1
          Endif
     Endif
Return

is_button_two_pressed:
     buttonpressed = 0
     If PORTA.2 = 1 Then
          'debounce the button press
          WaitMs 1
          If PORTA.2 = 1 Then
               While PORTA.2 = 1
                    'do nothing
               Wend
               buttonpressed = 1
          Endif
     Endif
Return

is_button_three_pressed:
     buttonpressed = 0
     If PORTA.3 = 1 Then
          'debounce the button press
          WaitMs 1
          If PORTA.3 = 1 Then
               While PORTA.3 = 1
                    'do nothing
               Wend
               buttonpressed = 1
          Endif
     Endif
Return

preload_timer:
     'at 4mhz (crystal) we have 1m clock cycles per second
     'if we count up to 1,000 that's 1 millisecond
     'so let's preload timer1 with 65,535 - 1,000 = 64,535
     '(which in hex is 0xFC17)
     TMR1H = 0xfc
     TMR1L = 0x17
Return

turn_uv_off:
     Low uv_led_pin1
     Low uv_led_pin2
     Low uv_led_pin3
     Low uv_led_pin4
     Low uv_led_pin5
     Low uv_led_pin6
     Low uv_led_pin7
     Low uv_led_pin8
Return

turn_uv_on:
     High uv_led_pin1
     High uv_led_pin2
     High uv_led_pin3
     High uv_led_pin4
     High uv_led_pin5
     High uv_led_pin6
     High uv_led_pin7
     High uv_led_pin8
Return

On Interrupt
     Save System
     If PIR1.TMR1IF = 1 Then
          PIR1.TMR1IF = 0
          Gosub preload_timer
          seccount = seccount + 1
          If seccount >= 1000 Then
               seccount = 0
               Gosub decrease_time_one_sec
               redrawtimer = 1
          Endif

          led_counter = led_counter + 1
          If led_counter > 10 Then led_counter = 0

          Select Case state
               Case 4
               If led_counter >= brightwhite And brightwhite < 10 Then
                    Low white_led_pin
               Else
                    High white_led_pin
               Endif

               Case 5
               If led_counter >= brightuv And brightuv < 10 Then
                    Gosub turn_uv_off
               Else
                    Gosub turn_uv_on
               Endif
          EndSelect

     Endif
Resume


Here's a video showing the final UV controller:



In the end we went for a 10ms PWM on the LEDs. Originally we thought 30 would be fine, but it introduced noticeable flicker on the LEDs when running normally. Although the camera shows flicker on the white LEDs, in reality they appear as uniformly lit.

The video demonstrates:

  • variable UV brightness
  • changing the countdown time-out value
  • an amazingly melodic tune when the countdown has elapsed 
  • using variable brightness white LEDs for aligning silkscreen masks before exposure


That's all the electronics done - now we need to find a suitable box to enclose it all in and actually start making some silkscreens!