photosensitive pcb exposure unit

This an exposure unit for photosensitive pcbs using some black light  CCFLs , a PIC microcontroler and a 2×16 LCD display

The project is made by some parts laying around the house except the CCFLs (Cold Cathode Fluorescent Lamps)  , you know  the ones people put on their pimped out PCs ,that i bought from a computer store.

For some time i was making my own pcbs and one of the most difficult things was to transfer the design on the pcb quickly and with the highest detail possible.After trying  many different methods i ended up using  the photoresist method

 

For this method  you use a pcb with a special coating that gets “destroyed” when its hit by uv light so you can print the pcb design on transparency paper and then place this transparency on top of the photosensitive board so it shades the pattern on the board.

After the exposure method you have to develop the pcb , for this you can use either sodium hydroxide or hydrochloric acid mixed with hydrogen peroxide . After the developing the only thing that remains is the etching.

You can find many tutorials out there about developing your own pcbs using the photoresist method.

The project is based around an old time classic PIC16F628 (initialy was using a pic16f84) driving some relays that power the CCFLs . Also theres a generic 16×2 lcd and 3 buttons. I found an old PSU from a broken arcade machine to power the device.

 

It`s casing was an old pc case that i made holes for the lcd and the buttons on its top.It was the first time to use an old computer case to build something and its not going to be the last.Ok the left and right sides suck but there’s a lot of room for improvement!

The main opening and shel for the pcb

Inside the main opening are 6 lamps 3 on the bottom and 3 on top.These can be turned on or off according to the pcbs needs (if you have double or single side pcb).

 

 

The photosensitive pcb is sandwitched between 2 pieces of glass along with the transparency film of the printed pattern (sometimes i use 2 films to build more contrast) and then its clamped using 4 big paper clips.The all this is slided into the “oven” on top of a shelf made of thick wire to hold it in the middle of the main opening.

 

Its been years since this project was finished so some some files cannot be found (for example the board design) but i managed to unearth the correct schematic and the correct hex file.Also the source files where found.

and here is the .hex file , its for a PIC16F682A. You must use an external programmer to program the chip since no special pins where included (ok you can add some if you want to build it).Also the whole source files can be downloaded here

See it in action in the video below

The program was written using PICC and consists of one main file and 3 included files in the include folder

uv_exposure_timer.c
|
|_ includes/
|
– flex_KBD.C
– flex_lcd.c
– proc.c

The libraries where found in the picc forum

uv_exposure_timer.c

//////////////////////////////////////////////////////////////////////////////
//Source Code Filename : uv_exposure_timer                                  //
//Author/Date/Version : George Droulias 2/11/2010 / Version 1.0 audio only  //
//Program Description : Timer for UV exposure unit controling 3 compinations//
//                      of UV lamps with sound signals  Ver 01              //
// Hardware / Simulation : devboard1 /uv exposure timer                     //
/////////////////////////mcu configuration,includes //////////////////////////

#include "16F628A.h"                 //Specify PIC MCU
#use delay(clock=4000000)          //clock 4Mhz and other #use library routines
#FUSES XT
#FUSES NOWDT
#FUSES NOPUT
#FUSES NOPROTECT
#FUSES NOBROWNOUT
#FUSES MCLR
#FUSES NOLVP
#FUSES NOCPD

// lcd configuration (4-bit)
#define LCD_ENABLE_PIN  PIN_B5
#define LCD_RS_PIN      PIN_B4
#define LCD_RW_PIN      PIN_B6

// driver includes
#include "includes/flex_lcd.c"
#include "includes/flex_KBD.C"

// Global variables
 unsigned int8 tone,time,s1,s2,n,t,J;
 char k;
 
// function includes
#include "includes/proc.c"

//Main block
void main(){
 startup(); //initiate proccess
 again:
 s1=setting1();
 s2=setting2();
 start_proc(s1,s2);
  while(TRUE){
      k=kbd_getc();
      if(k=='S'){
         goto again;
      } else {
         continue;
      }
   }
 
}

proc.c

////////////////////////// Function declares //////////////////////////////////
void beep() 
{
   for(tone=0;tone<50;tone++) {
      output_high(PIN_A0);
      delay_us(100);
      output_low(PIN_A0);
      delay_us(100);
   }
}

void beep_long() 
{
   for(tone=0;tone<255;tone++) { output_high(PIN_A0); delay_us(100); output_low(PIN_A0); delay_us(100); output_high(PIN_A0); delay_us(100); output_low(PIN_A0); delay_us(100); } } void startup() { kbd_init(); //keyboard init lcd_init(); //lcd init &welcome message lcd_putc("\fUV Exposure unit\n"); delay_ms(3000); lcd_putc("*Ver D1.0 2010*"); delay_ms(3000); lcd_putc("\f"); }//end startup float setting1(){ //Loop1:SET pressed define j using arrows and break if SET is pressed and j>0
  lcd_putc("\fPress any key");
  j=0;
  while(TRUE){
   k = kbd_getc();
   if(k !=0){
      if(k=='U'){
         beep();
         if(j<6){ j++; } else if(j==6){ j=1; } } if(k=='D'){ if(j>0){
            beep();
            j--;
         } else if(j==1){
            j=6;
         }
      }
      
      lcd_putc("\fSelected UVs:\n");
      switch(j){
      case 0:
      lcd_putc("Not set");
      break;
      case 1:
      lcd_putc("Top edge");
      break;
      case 2:
      lcd_putc("Bottom edge");
      break;
      case 3:
      lcd_putc("Middle only");
      break;
      case 4:
      lcd_putc("Top & middle");
      break;
      case 5:
      lcd_putc("Bottom & middle");
      break;
      case 6:
      lcd_putc("Full set");
      break;
      }
       if(j>0){
         if(k=='S'){
            beep();
            break;
         }
      }
   }
   delay_ms(10);
  }
  
  //Loop2
  lcd_putc("\fUV lamp settings\nsaved...");
  delay_ms(2000);
  return(j);
  break;
}

float setting2(){
   lcd_putc("\fPress any key\nfor exp time");
   time=0;
   //loop1 set time
   while(TRUE){
      k=kbd_getc();
      if(k !=0) {
         lcd_putc("\fExposure time:\n");
         if(k=='U') {
            beep();
            time=time+5;
         }
         if(k=='D'){
            if(time>0){
               beep();
               time=time-5;
            } else {
               time=60;
            }
         }
         if(k=='S'){
            if(time>0){
               beep();
               break;
            }
         }
         delay_ms(10);
         printf(lcd_putc,"   %2u min",time);
      }
   }//loop1
   
   lcd_putc("\fTimer settings \nsaved...");
   delay_ms(2000);
   return(time);
   break;
}

void UV_control(j,state){
   if(state ==1){
      switch(j){
         case 1:
         output_low(PIN_A1);
         output_high(PIN_A2);
         output_low(PIN_A3);
         break;
         case 2:
         output_low(PIN_A1);
         output_low(PIN_A2);
         output_high(PIN_A3);
         break;
         case 3:
         output_high(PIN_A1);
         output_low(PIN_A2);
         output_low(PIN_A3);
         break;
         case 4:
         output_high(PIN_A1);
         output_high(PIN_A2);
         output_low(PIN_A3);
         break;
         case 5:
         output_high(PIN_A1);
         output_low(PIN_A2);
         output_high(PIN_A3);
         break;
         case 6:
         output_high(PIN_A1);
         output_high(PIN_A2);
         output_high(PIN_A3);
         break;  
      }
    } else {
      output_low(PIN_A1);
      output_low(PIN_A2);
      output_low(PIN_A3);
    }
}

void UV_preheat(n) {
  for (;n!=0; n--){
   lcd_putc("\fPre-heating UV\n");
   printf(lcd_putc,"%u min left",n);
   delay_ms( 60000 );
  }
}

void UV_exposure(n) {
  for (;n!=0; n--){
   lcd_putc("\fPCB exposure UV\n");
   printf(lcd_putc,"%u min left",n);
   delay_ms( 60000 );
  }
}

void alarm(t){
   for (;t!=0; t--){
      beep_long();
      delay_ms(150);
      beep_long();
      delay_ms(1000);
   }
}

void check_START(){
      while(TRUE){ 
      k=kbd_getc();
      if(k !=0) {
         if(k=='S') {
            beep();
            break;
         }
      }
      delay_ms(10);
   }
}
void start_proc(j,time){
   //preheat uv lamps for 3 minutes and beep
   UV_control(j,1);
   UV_preheat(2);
   lcd_putc("\fReady...");
   alarm(10);
   lcd_putc("\fInsert PCB\nand press START");
   check_START();
   UV_exposure(time);
   lcd_putc("\fUV Exposure\n Complete");
   UV_control(j,0);
   alarm(10);
   delay_ms(5000);
   break;
}

flex_KBD.C

/////////////////////////////////////////////////////////////////////////// 
////                             Flex_KBD.C                            //// 
////                  Generic keypad scan driver                       //// 
////                                                                   //// 
////  kbd_init()   Must be called before any other function.           //// 
////                                                                   //// 
////  c = kbd_getc(c)  Will return a key value if pressed or /0 if not //// 
////                   This function should be called frequently so as //// 
////                   not to miss a key press.                        //// 
////                                                                   //// 
/////////////////////////////////////////////////////////////////////////// 



//Keypad connection:  

#define col0 PIN_B1
#define col1 PIN_B2 
#define col2 PIN_B3 
#define row0 PIN_B4 
#define row1 PIN_B5 
#define row2 PIN_B0 
#define row3 PIN_B7 

// Keypad layout: 
char const KEYS[4][3] = {{'U','D','S'}, 
                         {'G','5','6'}, 
                         {'7','8','9'}, 
                         {'*','0','#'}};

#define KBD_DEBOUNCE_FACTOR 10    // Set this number to apx n/333 where 
                                  // n is the number of times you expect 
                                  // to call kbd_getc each second 



void kbd_init() { 
} 


short int ALL_ROWS (void) 
{ 
   if (input (row0) & input (row1) & input (row2) & input (row3)) 
      return (0); 
   else 
      return (1); 
} 



char kbd_getc( ) { 
   static byte kbd_call_count; 
   static short int kbd_down; 
   static char last_key; 
   static byte col; 

   byte kchar; 
   byte row; 

   kchar='\0'; 
   if(++kbd_call_count>KBD_DEBOUNCE_FACTOR) { 
       switch (col) { 
         case 0   : output_low(col0); 
               output_high(col1); 
               output_high(col2); 
                    break; 
         case 1   : output_high(col0); 
               output_low(col1); 
               output_high(col2); 
                    break; 
         case 2   : output_high(col0); 
               output_high(col1); 
               output_low(col2); 
                    break; 
       } 

       if(kbd_down) { 
         if(!ALL_ROWS()) { 
           kbd_down=false; 
           kchar=last_key; 
           last_key='\0'; 
         } 
       } else { 
          if(ALL_ROWS()) { 
             if(!input (row0)) 
               row=0; 
             else if(!input (row1)) 
               row=1; 
             else if(!input (row2)) 
               row=2; 
             else if(!input (row3)) 
               row=3; 
             last_key =KEYS[row][col]; 
             kbd_down = true; 
          } else { 
             ++col; 
             if(col==3) 
               col=0; 
          } 
       } 
      kbd_call_count=0; 
   } 
  return(kchar); 
}

flex_lcd.c

// flex_lcd.c

// These pins are for the Microchip PicDem2-Plus board,
// which is what I used to test the driver.  Change these
// pins to fit your own board.

#define LCD_DB4   PIN_B0
#define LCD_DB5   PIN_B1
#define LCD_DB6   PIN_B2
#define LCD_DB7   PIN_B3

#define LCD_E     PIN_B6
#define LCD_RS    PIN_B4
#define LCD_RW    PIN_B5

// If you only want a 6-pin interface to your LCD, then
// connect the R/W pin on the LCD to ground, and comment
// out the following line.

#define USE_LCD_RW   1     

//========================================

#define lcd_type 2        // 0=5x7, 1=5x10, 2=2 lines
#define lcd_line_two 0x40 // LCD RAM address for the 2nd line


int8 const LCD_INIT_STRING[4] =
{
 0x20 | (lcd_type << 2), // Func set: 4-bit, 2 lines, 5x8 dots
 0xc,                    // Display on
 1,                      // Clear display
 6                       // Increment cursor
 };
                             

//-------------------------------------
void lcd_send_nibble(int8 nibble)
{
// Note:  !! converts an integer expression
// to a boolean (1 or 0).
 output_bit(LCD_DB4, !!(nibble & 1));
 output_bit(LCD_DB5, !!(nibble & 2)); 
 output_bit(LCD_DB6, !!(nibble & 4));   
 output_bit(LCD_DB7, !!(nibble & 8));   

 delay_cycles(1);
 output_high(LCD_E);
 delay_us(2);
 output_low(LCD_E);
}

//-----------------------------------
// This sub-routine is only called by lcd_read_byte().
// It's not a stand-alone routine.  For example, the
// R/W signal is set high by lcd_read_byte() before
// this routine is called.     

#ifdef USE_LCD_RW
int8 lcd_read_nibble(void)
{
int8 retval;
// Create bit variables so that we can easily set
// individual bits in the retval variable.
#bit retval_0 = retval.0
#bit retval_1 = retval.1
#bit retval_2 = retval.2
#bit retval_3 = retval.3

retval = 0;
   
output_high(LCD_E);
delay_cycles(1);

retval_0 = input(LCD_DB4);
retval_1 = input(LCD_DB5);
retval_2 = input(LCD_DB6);
retval_3 = input(LCD_DB7);
 
output_low(LCD_E);
   
return(retval);   
}   
#endif

//---------------------------------------
// Read a byte from the LCD and return it.

#ifdef USE_LCD_RW
int8 lcd_read_byte(void)
{
int8 low;
int8 high;

output_high(LCD_RW);
delay_cycles(1);

high = lcd_read_nibble();

low = lcd_read_nibble();

return( (high<<4) | low); } #endif //---------------------------------------- // Send a byte to the LCD. void lcd_send_byte(int8 address, int8 n) { output_low(LCD_RS); #ifdef USE_LCD_RW while(bit_test(lcd_read_byte(),7)) ; #else delay_us(60); #endif if(address) output_high(LCD_RS); else output_low(LCD_RS); delay_cycles(1); #ifdef USE_LCD_RW output_low(LCD_RW); delay_cycles(1); #endif output_low(LCD_E); lcd_send_nibble(n >> 4);
lcd_send_nibble(n & 0xf);
}

//----------------------------
void lcd_init(void)
{
int8 i;

output_low(LCD_RS);

#ifdef USE_LCD_RW
output_low(LCD_RW);
#endif

output_low(LCD_E);

delay_ms(15);

for(i=0 ;i < 3; i++)
   {
    lcd_send_nibble(0x03);
    delay_ms(5);
   }

lcd_send_nibble(0x02);

for(i=0; i < sizeof(LCD_INIT_STRING); i++)
   {
    lcd_send_byte(0, LCD_INIT_STRING[i]);
   
    // If the R/W signal is not used, then
    // the busy bit can't be polled.  One of
    // the init commands takes longer than
    // the hard-coded delay of 60 us, so in
    // that case, lets just do a 5 ms delay
    // after all four of them.
    #ifndef USE_LCD_RW
    delay_ms(5);
    #endif
   }

}

//----------------------------

void lcd_gotoxy(int8 x, int8 y)
{
int8 address;

if(y != 1)
   address = lcd_line_two;
else
   address=0;

address += x-1;
lcd_send_byte(0, 0x80 | address);
}

//-----------------------------
void lcd_putc(char c)
{
 switch(c)
   {
    case '\f':
      lcd_send_byte(0,1);
      delay_ms(2);
      break;
   
    case '\n':
       lcd_gotoxy(1,2);
       break;
   
    case '\b':
       lcd_send_byte(0,0x10);
       break;
   
    default:
       lcd_send_byte(1,c);
       break;
   }
}

//------------------------------
#ifdef USE_LCD_RW
char lcd_getc(int8 x, int8 y)
{
char value;

lcd_gotoxy(x,y);

// Wait until busy flag is low.
while(bit_test(lcd_read_byte(),7)); 

output_high(LCD_RS);
value = lcd_read_byte();
output_low(lcd_RS);

return(value);
}
#endif

it was a usefull device that i keep using besides some bugs that exist due to bad power supply and noise from the relays. Someday i will try to renew it a little bit..