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!

Using PIC Timer1 for PWM

At the heart of our UV controller is the Timer1 module and some clever use of PWM.
Let's look at PWM first of all - this is how we keep the lights on. The basic idea is this - persistence of vision gives us the illusion that an LED is light either dimly or brightly, when in fact it's flickering on and off so quickly that we can't see it flashing. The more time it spends on, the brighter it appears:




This is how most people imagine PWM working - in a "serial" manner (one instruction after another, with a suitable delay built in). For an 80% duty cycle, for example, the code might go something like this:

LED_on
Wait 1ms

LED_on
Wait 1ms
LED_on
Wait 1ms
LED_on
Wait 1ms
LED_off
Wait 1ms

Or even, more simply
LED_on
Wait 4ms
LED_off
Wait 1ms

and just repeat/loop.
In fact, a lot of servo controllers work this way - which is where the theoretical limit of 8 serially controlled servos per board comes from. A lot of times you see this for pwm - the code exactly matches the timing graph: send a pin high, wait the appropriate of time, send it low, wait out the remainder of the total cycle time.

And it works. But it's not the best way to control stuff via PWM. Let's say we've got some other stuff going on (like reading an analogue input, writing to a character display and so on). What happens now?

LED_on
Wait 4ms
LED_off
Wait 1ms
Check the analogue input
Write to the display
Loop

The problem is that doing all the other stuff, not just flickering the LEDS, takes up a lot of time. So our LEDs are on for 4ms and off for 1ms, and on for 4 out of 5 milliseconds gives us a duty cycle of 80% over 5ms. But if all this other stuff takes a few milliseconds to complete too:

On for 4ms
Off for 1ms
Stays off for 20ms while we check the inputs, update the LCD display and so on
Our 80% duty cycle is now actually on for 4ms, off for 1ms, stays off for 20ms, so on for 4 out of 25ms gives us a duty cycle of just 16%. And, because we've increased the total amount of time between each cycle, the flickering of the LEDs, on and off, becomes noticeable to the eye.

What we need is a way of getting the LEDs to update themselves, while we dedicate as much mcu power and time to the other tasks as necessary. What we need, in short, is a timer interrupt!

In our UV controller we're going to use our timer to do two things. Firstly it'll keep track of time (since we have a countdown timer from switching on the UV LEDs so that we don't overexpose any screens/boards). But secondly, we'll use it to as a "clock" for our PWM. 

Instead of saying "turn on, do nothing while a set time elapses, turn off, do nothing while more time passes" we're going to use an interrupt, which is more akin to: "turn on the LEDs, then I'm off over here to do something else - give me a shout when that time is up". After the "on" part of the PWM time has elapsed "turn off the LEDs, then give me a shout when it's time to turn them on again".

In fact, we're going to use a slightly hybrid version of the two approaches. We're using an interrupt to run some code every 1 millisecond. At this point we'll ask "should the LEDs be on of off?" and act accordingly. We'll also keep a counter running and increase it by one; when the counter hits 1000, we know that exactly one second has passed, and adjust our countdown time by one second.  Here's how we set up Timer1 in Oshonsoft:


'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
'clear the timer-has-tripped interrupt flag
PIR1.TMR1IF = 0


Our sub-routine preload_timer simply sets a value so that we get the timer interrupt to fire every millisecond. This can take a bit of thinking about, and there are "make-it-easy" calculators all over the 'net but they can sometimes just confuse the matter. Simply put, Timer1 is actually a 16-bit counter. Every clock instruction it increases by one. When it reaches 65,535 and rolls over to zero, the Timer1 interrupt is called. That's basically it.

What we need to do is work out what number to start counting from, so that when the mcu hits 65535 limit and rolls over to zero, exactly the right number of clock instructions have been carried out to indicate one second has elapsed. The number we need to count up to is critically dependent on the speed we're running the mcu at (the crystal value we're running it from). In our case, we're using a 4Mhz crystal.

A 4Mhz crystal means 1 million instructions are carried out every second. So 1000 instruction cycles (one instruction takes 4 clock cycles remember!) occur every millisecond. If we set our Timer1 value so that it only counts up to 1,000 instead of 65,535 before firing the interrupt, we should get a 1ms interrupt. Instead of starting at zero, we need to preload our Timer1 value with 65535-1000 = 64535. In hex, 64535 is 0xFC17

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

Now, every millisecond, wherever we are in our program, and whatever we're doing, everything will get parked and code execution jumps to the interrupt routine. When we're done here, we'll jump back to exactly where we were in the code, before we were so rudely interrupted. The interrupt routine needs to keep track of the number of milliseconds passed, and update the LEDs as necessary:


On Interrupt
     Save System
     
     'If it's Timer1 that caused the interrupt:
     If PIR1.TMR1IF = 1 Then
          'set the new timer value
          Gosub preload_timer
         
          'clear the interrupt flag
          PIR1.TMR1IF = 0
         
          'increase our (milli)second timer
          seccount = seccount + 1
          'if one whole second has passed, update the clock
          If seccount >= 1000 Then
               seccount = 0
               Gosub decrease_time_one_sec
               redrawtimer = 1
          Endif
          
          'increase our LED PWM wave
          led_counter = led_counter + 1
          If led_counter > 30 Then led_counter = 0

          'decide whether the LED(s) should be on or off
          Select Case state
               Case 4
               If led_counter >= brightwhite And brightwhite < 30 Then
                    Low white_led_pin
               Else
                    High white_led_pin
               Endif

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

     Endif
Resume

In this way, we still have PWM controlled LEDs - they are on for part of a time and off for a specific set time - but instead of just hanging around doing nothing between the on and off phases, we're freed up to run other code and the LEDs will just "look after themselves".

As well as a millisecond counter, we'll just keep an "LED brightness counter" which also increases every millisecond - but only counts up to 30 then resets to zero instead of 1,000 (as our clock counter does).  In our code, we're using a brightness level from zero to 30. All we do is see if the current LED brightness counter is less than the required LED brightness and if it is, turn the LEDs on, otherwise turn them off.

This means that one PWM cycle lasts 30 milliseconds (we don't want to make this too large else visible flickering will appear on the LEDs). We don't have a fine degree of control over the "duty cycle". We can't, for example, set it to 1%, but we have up to 30 different values we can use which equate to
1/30 = about 3%
2/30 = about 7%
3/30 = about 10%
4/30 = about 13%
and so on.

Enough with the theory - let's get this coded up and some new firmware burned onto our almost-working hardware!

Wednesday 24 April 2013

Working UV controller (sort of)

After making a PCB and finding that it didn't work, and then making up a breadboard version of the circuit and making it work, we're back to making a PCB version of the UV controller circuit - and hoping that it works this time!


Here's a new controller PCB with some simplified firmware on it, simply writing a message to the character LCD. With a bit of jiggery-pokery we managed to get the display to work on our new PCB.


Wiring up the rest of the circuit and putting it on it's own dedicated power supply (running it off a little PIC programmer isn't going to be a good idea if all these LEDs suddenly spring into life!) and everything is still working fine:


The only problem now, of course, is we're running simplified firmware which just writes "Hello" onto the character LCD - the darlington arrays aren't being set so random lines of UV LEDs currently light up each time the circuit is powered up.


But it's encouraging to see that the whole thing still (appears to be) working. Even with a couple of rows of LEDs lit (we'll only ever have one row lit at any one time, and strobe them quickly using PWM to control the brightness) the message on screen remains the same, and we put a start-up sequence in place so that we could see if the PIC is rebooting. It's possible that when a row of LEDs lights up it might create a dip in the voltage supply and reset the microcontroller. A few smoothing caps on the supply line(s) - you can see one on the supply to the LCD in the photo above) and everything is good - no mcu resetting, no ripples, nothing.

So in theory all that remains is to put the "proper" firmware onto the PIC and shove the whole lot into an box and we're ready to try creating our own miniature silkscreen for PCB printing....

16x2 character LCD R/W pin is important

Today's lesson is don't be a jerk and always breadboard your prototypes first. It doesn't matter how clever you think you are (read: I think I am) nor how many successful circuits you've made in the past, test stuff before committing to a PCB design. It's easy to say that now....

Despite protestations from a few others, I was confident that the uv controller circuit was relatively simple and didn't need breadboarding. After all, we've made a few different circuits with character LCDs, we've made pcbs with transistors and speakers on acting as crude amplifiers. We've made no end of twisty-pots-as-input type devices. It's surely just a case of pulling all these bits together? Why waste time plugging wires into a board when you know it'll work anyway....?

Well, the first attempt just didn't work, and it took me about two hours to discover why - eventually (and reluctantly) making up the entire circuit on a breadboard to do some debugging.

As is often the case, the problem came down to a faulty assumption. I was assuming that because we never read data from the character display, only write to it, that the R/W pin didn't need to be controlled from the PIC - just tie it to ground (the datasheet said 0=write, 1=read) and could save a pin on (or rather save time running a trace back to) the mcu. The only change I made on the breadboard prototype (keeping the original firmware from the non-working board) was that the RW pin was connected (in firmware I left the code for controlling it in the first time around, just never bothered connecting the r/w pin on the LCD to the PIC). Everything else was exactly the same as before.


And there you have it - a working character LCD (complete with blinking LED to show us that the PIC is alive and running code!). And all because of a faulty assumption and a determination to save a few minutes assembly by leaving out the R/W pin.

That worked out well then.

Tuesday 23 April 2013

UV lightbox controller

Another update on our UV lightbox controller. We coded up some firmware, finished soldering the board, added the buttons and potentiometers and connected up the 16x2 character LCD.


The speaker was added as something of an afterthought, but it might be nice to have a sounder when the timer expires to indicate the end of UV exposure time. The LCD has a 0.1" pin header soldered across pins 1-16 and is connected via an IDE ribbon cable soldered directly to the PCB. We powered up the board and....


...no dice. After tweaking the contrast on the display, we saw only a bunch of empty blocks. For this reason, we're not posting the schematics or firmware here, until something is working! But we know for sure that at least the backlight is connected correctly.


But just as soon as it is, we'll have a follow up post with more details!

Laser cutting mirorred acrylic

When cutting mirrored acrylic, always cut with the mirrored side down. This means you need to flip the text so it cuts out the right way around! It only took us one attempt to get it wrong before cutting out a massive 58 different names for wedding table place holders.


28 minutes of solid lasering at 18mm/sec and 15mA and the entire A4 sheet was done


Another eight minutes and 58 bases were cut out from black acrylic too. The end result(s) look something like this:


The font used was Cheri and the letters were separated in Inkscape and pushed together to make one continuous shape ready for cutting. About an hour's work, and we've all the place-names to make one couple even happier on their big day!

Making through-hole LEDs from SMT versions

Often when people make the move from through-hole components to SMT, there's no going back. Why bother with all that messy drilling and flipping boards over and soldering and trimming legs, when you can simply plop a miniaturised component onto the board with a bit of paste and solder it up in a fraction of the time?

But because our prototype PCB for the board game module is single-sided, we're having to solder little wires from the "under" side of the board to the "top" to solder our tiny little SMT LEDs to. In short, we're making through-hole style LEDs from some 1206-sized SMT versions



This allows us to mount each LED in the tiny space alongside each of the (5mm high) pushbuttons. Regular LEDs - even the small 3mm versions - are far too tall and we didn't fancy trying mounting the LEDs on their side to get them to fit, so the obvious answer was to make some ultra-low profile through-hole LEDs.


The final board, soldered up and ready for testing:


In the final (production) version, we're going to make the PCB double-sided and put SMT pads on the top surface alongside the pushbuttons. This should not only make assembly easier, but much quicker in the long run!

Sunday 21 April 2013

Justin's LED flashing UFO birthday card

Ok, maybe the red LED is just a little brighter - or perhaps should have been put on one of the "wider" or closer-looking beams. Or maybe replaced with a green/yellow LED. Or maybe had a higher value inline resistor or something.

But the end result is pretty cool....

Friday 19 April 2013

UFO birthday card with flashing LEDs - final pictures

Last night, at BuildBrighton, we helped Justin to get his flashing-LEDs-and-UFOs birthday card ready for this weekend. And, as ever, Steve made it not just look like a finished "product" but like the most awesome you wished you owned yourself. Where most of us would just slap a sticker on the top and call it done, Steve went the extra mile to make it really special!


The key to making this look so awesome was the thin layer of styrene immediately over the top of the LEDs - providing a single, flat surface for the image to sit on top of.


A photo-quality print is lined up and fixed over the entire board using Photo Mount spray...


... and the excess trimmed off. The print on the surface is pale enough that the LEDs shine through the relatively lightweight (90gsm?) photo paper meaning no nasty cutouts or holes required in the image surface.


Instead of just stopping there, Steve went to the trouble of making a striking green frame for the image, complete with sliding on-off switch to stop the battery from draining too quickly.


 With a 5v regulator onboard, the LEDs can be powered from a single PP3 9v battery.
Having mounted the image onto the uprights, a simple - but effective - acrylic border is placed around the outside of the photo and fixed in place with hot glue.



The final (amazing-looking) finished product. Hopefully we've done enough to help Justin earn a few brownie points for this one ;-)


LED UFO birthday greetings card

Thursday nights are BuildBrighton Open Nights, which means getting stuck in and helping out with other people's projects a lot of the time. Last night it happened to be Justin who needed a little help completing a fancy greetings card. It's his girlfriend's birthday soon and which girl couldn't help but be impressed by a hand-made birthday card? With UFOs on it. And flashing LEDs? (no, seriously, she loves this kind of stuff).

So Justin picked an image of the interwebs and set about making a design. He found a picture of some UFOs and decided to have a number of LEDs light up in a sequence, to give the image of "tractor beams" rising up from the ground:


The basic idea is that at any one time, all the same numbered LEDs are on together. So firstly all the ones come on, then a few hundred milliseconds later, all the twos, then all the threes and so on. This gives each beam it's own animation, while allowing multiple beams to be controlled from a single PIC output pin.

It's going to be a battery operated card, and - unlike an earlier, similar project - we've convinced Justin to invest some time in making a PCB rather than just poking LEDs into the card and soldering them all up with wire on the back! We're trying to avoid through hole components here, because of the little spiky legs on the back - so its SMT all the way; for LEDs, resistors and microcontroller. Initially we thought of using the 16F1825 with it's low pin count, but in the end opted for a 16F628a because it runs from an internal oscillator (no need for an external, chunky crystal) at the lower rate of 4Mhz, not 32Mhz as per the '1825. Nothing on the card is time critical (it has no serial comms, for example) and, generally, the lower clock speed means lower power consumption when running (so hopefully those LEDs can stay flashing for a little bit longer).

Normally we wouldn't bother making up a schematic for something like this - it's just bunches of LEDs being driven directly from the output pin of a PIC. But the offset/staggered nature could cause a few headaches when laying out the board, so out came ExpressPCB...


(we've drawn every bunch of LEDs as four, although sometimes on the card, only two or three LEDs light up at a time - it was just easier to draw them all in - copy and paste went mad - and then just not include them on the PCB later)

While it's a really simple circuit, the key thing is that when this is linked to the PCB layout software in ExpressPCB, it, rather handily, lights up all connected pads when you click on them. This makes laying out our PCB so much easier than just trying to do it all in one go off the top your head! The numbering is a bit weird - we used 1,2,3,4 for the first "row" of LEDs, then 11,12,13,14 for the next row and so on. So when we come to run the code, we'll switch on 1,2,3,4 together, then 11,12,13,14 together, then 21,22,23,24 - basically the "tens column" of the number represents the current row to be lit.

Because the LEDs need to line up with a printed image, it was important to position each LED exactly in the right place. We used our old friend Inksc(r)ape to do this. First, we imported the bitmap to be used and resized it to the size of our eurocard copper board (100mm x 160mm). Then we simply draw 1206 sized rectangles and positioned them over the image:


The painstakingly slow bit was selecting each LEDs, noting it's position (in mm) and transferring these positions to the 1206 LEDs drawn in ExpressPCB.


It was slow going, but we got there in the end!


Because the schematic and the PCB layout were linked, whenever a single pad on one LED was clicked, all the others that it needed to be connected to lit up. This made routing the traces a little bit easier, and also ensured that no LED was inadvertently left out


To make assembly easier later on down the line, we positioned every LED with the ground pin on the same side; it would have made routing easier not to do this, but there would be nothing worse than ending up with a failed board because one or more LEDs were soldered the wrong way around!

So after about an hour or so of routing and re-routing traces, we ended up with a single-sided PCB with no jumpers (since the board will be hand assembled, vias were a big no-no as they'd introduce more nasty spiky bits where bits of wires had to be soldered)

The PCB as drawn in ExpressPCB

Because we're using press-n-peel (which reverses an image during transfer) AND entirely surface mount components, the whole PCB needs to be flipped before printing (so after transfer, it's back the right way around)



That's all the hard work done! Now it's just a matter of etching the board and sticking the LEDs onto it! Here's the board fresh from the ferric chloride:

(there's a bit of pitting in places on the board - our new Brother 3012 isn't a patch on the old Xerox Phaser for printing toner-transfer images)

Justin made a great job of soldering all the components in place. Using a bit of wire connected to a 3v coincell battery he made sure all the LEDs were connected the right way around and working before the last stage of the project - programming the PIC.

The firmware is incredibly simple. Simply flash PORTB.0 high, wait a few milliseconds, send PORTB.0 low and PORTB.1 high - repeat for each pin 0-6 and cycle forever. Rather than use an if statement to determine which pin to light up, we used bit-shifting and wrote a single byte value to the entire port:

Define CONF_WORD = 0x3f18
Define CLOCK_FREQUENCY = 4
AllDigital

Config PORTB = Output
PORTB = 0x00

Dim counter As Byte
Dim tmp As Byte
WaitMs 1000


init:
     counter = 0
     
loop:
     
     tmp = 1
     tmp = ShiftLeft(tmp, counter)
     PORTB = tmp

     counter = counter + 1
     If counter > 6 Then counter = 0
     WaitMs 200
          
Goto loop
End
     


The PIC was programmed using a SOIC-clip and a homemade programming cable (the PIC was already mounted onto the PCB before it was programmed).



And there you have it. A cycling, flashing LED battery-operated UFO-themed greetings card. And all for a few hours work.


As it was getting late by the time the board was assembled and working, Steve took it away to produce the sticker front and hid all the scary looking electronics away. Hopefully a follow up post will show the final, working card in all it's glory.....

Tuesday 16 April 2013

UV exposure box controller

We found an old 16x2 character LCD display knocking around from a previous project which already had a 16-way 0.1" pin header soldered to it. It seemed a shame to mess it up so we left the pin header in place and  hacked an IDE ribbon cable to fit it. A two-row IDE connector simply connects each pin on a single row to alternate strands on the ribbon:


(since we're using a 4-bit interface we chopped a couple of strands back as they are not needed)

We've gone a bit off-plan with this board, making stuff up and dropping it onto the board, off-the cuff. Sometimes that means just tacking a few extra wires onto some pins (although mostly we end up doing that because we've not left enough power/ground pads to connect to!) and in this case, we've decide to add in a speaker (audible buzzer when time is up) and left wires trailing to connect our buttons and menu selector pot. The contrast for the LCD screen is controlled by the trimmer pot on the right - once it's set, we'll probably just leave it at that (maybe we could have even just created a simple voltage divider for contrast?)



In fact, it looks like our hardware is pretty much assembled and just needs a PIC and some firmware in there to get it working. Assuming it all works of course....

Soldering speakers can be difficult

...unless you've got a big blob of blu-tack to hold the pesky thing in place and stop it sticking to your soldering iron!


(Ok, the Brighton Poundland only sells white-tack, but the idea's the same!)

Monday 15 April 2013

A4 frame for eurocard sized PCB silkscreen printing

Thanks again to Andy at handprint.co.uk who emailed with some more advice about our miniature silkscreen idea. Below is an abridged copy of his email:


The size thing...
To get good results you need to have sufficient room on screen to load it with enough ink for several prints, enough space to have a good run up, and enough for a good follow through without bashing into the frame which will almost certainly mess the things up.

Also, there is "snap off". To get a nice clean print, the screen should only contact the substrate under the edge of the squeegee - as the squeegee passes over the screen it will pull the fabric in the direction of travel. When the squeegee has passed, the mesh will spring back and if it is still in contact with the substrate, it will smudge the ink which was laid down when the mesh was elongated. Getting optimum snap-off is problematic when using a small screen without sufficient "spare" between the image and the frame. Balancing the pressure required to ensure contact without overdoing it and squelching too much ink through is not easy.

Also, if you think about it you are deforming the fabric by printing with snap-off. The frame will prevent the fabric from deforming evenly at the edges, leading to distortion of the image.

Exposing the stencil is also harder with small screens. I expose my screens in a vacuum frame. This ensures that the film +ve is squashed tight against the emulsion layer and minimises the risk of the light undercutting the image. It's a pretty good vacuum, but even so, there is an area against the frame where the vacuum can't ensure contact, even on a good day! So it pays to have the edge of the image well away from the frame.

It may be that the accuracy you required doesn't merit taking account of all the stuff that I've been telling you, but I feel that it's good to come from a position of knowing what the ideal setup should be like and making modifications that you think you can get away with, rather than constructing something and trying to get it to work even though it has basic inbuilt flaws that only become apparent when you've built it and tried to use it!

Bye4now.



Well, since our PCB traces are often 0.5mm (and sometimes below that) accuracy is one thing we're keen to preserve. Allowing the silk to stretch to the printed surface and "snap-back" is also pretty important too - we don't want any chance of smudging these relatively small PCB traces at all. So we're going to need a bit more room around the outside of our copper board to get a really good print.


Following Andy's suggestions, we've redrawn our MDF laser-cut pattern to make the frame much larger, but still try to keep within a maximum of an A4 footprint.

Controller for UV exposure box for PCB screen printing

While it was really tempting to just chuck some LEDs onto a bit of veroboard and just plug it in (ok, we did exactly that) we've since decided that it needs a bit of time and effort spending on it. While we've got a board full of LEDs that lights up when you plug it in, it's time we put some of our super-cool geek-skills to use and actually made a half-decent box. So here's the plan....



We've got an array of 8 rows of 12 LEDs wired in parallel. Each LED has a current consumption of about 20mA. Each row uses about 240mA of current, and should we light the entire board in one go, that's 240*8 = 1.9amps. Not a massive amount of current, but enough to make us want to think about how we want to handle this amount of power consumption.

First up, instead of lighting all 8 rows in one go, we thought we could strobe them, one at a time, through a ULN2003A transistor array. These arrays can handle up to 500mA per "channel" (but only a total of about 650mA at any one time). So with a few transistor arrays (the ULN2003A has only seven, not eight, channels) that need to be switched on and off quite quickly, we're going to need a microcontroller.

Of course, it's going to be a PIC. At this stage, we've no idea what else we're going to want, so let's just shove a big 16F877A in there to give us some options....

  • What about adding a timer to the exposure box?
  • With a microcontroller, we could use a few 7-segment displays and have a count-down timer.
  • How about - since we're strobing the LEDs anyway - we use PWM to set the brightness?
  • While we're not using the UV, we could probably do with some white LEDs in there, to shine from underneath to help align our PCB designs with the silkscreen.

Our current idea is to have a character LCD display, some buttons (up, down, select) and a dial (to select the required function mode). Using the 16F877A we also have some hardware UART (serial comms) so should we want to in future, we could always hook the whole thing up to a PC or other controller.



All this sounds ok so far and leaves us plenty of options for expanding in the future. And if we're going to be changing things in future, perhaps it's as well to give SMT a miss for a change, and use some old-school through-hole components and sockets on our PCB. Yes, this is all starting to sound quite promising....

Here's a simple schematic for a PIC to control 8 rows of UV LEDs, one array of white LEDs, with buttons and a dial (potentiometer) mode-select:


And a press-n-peel ready PCB layout:



Here's how the components should be laid out:


The ground from each row of LEDs is connected to the LED connector pads on the right-hand edge of the board (which in turn are connected to the ULN2003A darlington arrays which allow us to light entire rows of LEDs as required). We've got a 16x2 character LCD connected along the top of the board, push-buttons connected (on wires) to the pull-down resistors and a potentiometer on analogue channel RA0 as our menu selector. Except for a few de-coupling/smoothing caps and a crystal, that's just about it.