Requirements For PCB Making Using Dry Film Technique
Creating a printed circuit board (PCB) with dry film method, could produce a high quality PCB. It's because of good copper track coating. Comparing to a simple toner transfer method, it's excellent in output but it costs more dollars and more time consuming.
![]() |
A sample a pre-fabricated PCB made by a Gerber viewer |
00:00 / 00:00
The dry film is made from polymer. it is photo sensitive to UV light. In industrial PCB making, dry film is choice of PCB copper tracking protecting. In the market it cost around 10 US Dollars for 30 cm by 5 meters.
![]() |
My dry film in use. It is 5 meters long. I bought it from E bay for 8 US Dollars. |
However using dry film for PCB making requires UV lamp with a precise timer to make the process more accurate. A low lost timer made by some Chinese manufacturer cost around 3 US Dollars.
The source of UV light beside the sun light are a UV light bulb or a popular UV LED available on line.
![]() |
A low cost Chinese light bulb works at 220V AC with the output power of 40 W. It's suitable for personal use. I use this device for my electronic workshop at home. |
![]() |
A sample PCB I made using dry film method with solder mask coating in 2008. |
Making A DIY UV Time Exposes Unit Using PIC16F876A
Design Requirements
For a normal control device, it require some user inputs, a display output and a driving output.
User input has no other words than a push button in common.
A display device, could be a simple seven-segment display because it has a big display value and very simple to program.
An output driving, commonly drive an AC load. In this project, it drive an output relay to switch the AC UV light bulb on and off.
To make an embedded controller project, we must thing about all controller's resource requirement that fit the project. For this simple project, an 8-bit PIC device is a good choice. Most 8-bit PIC has a rich of digital input output, analog inputs and PWM output driving.
I posses a lot of PIC16F876A I bought for my working project to some companies. I have left a dozen of it from the finished projects.
![]() |
A reference image of this device taken from RS components. Some components I possess are very old and used that is not suitable to post them here. |
The Completed Project
In this design, I use the following stuffs to make a timer work well for my personal use:
- A variable resistor used for adjust time
- An ENTER button used for inputting the time value
- A LOAD button used for reading the saved timing from previous setting
- An 8-digit multiplexed displays for display the time
- An output relay driver to switch the lamp on and off
- A buzzer alert the beginning and the end of the running timer
![]() |
The picture of the completed project |
The display digit are 0.36 inches diagonal. Conventionally, the multiplexing method could work well for only 8 digits.
![]() |
Schematic Diagram I designed in Proteus 8 |
![]() |
A sample PCB design the red line indicates the design rule error, but it doesn't matter. |
![]() |
A computer software rendered of this design |
I took the photo of my finished work as below.
MPLABX XC8 Source Code
The overall program is written using XC8 from the device vendor.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <xc.h> | |
#include "config.h" | |
#define _XTAL_FREQ 20000000 | |
#define oneSec 300 | |
#define SW1 PORTAbits.RA1 | |
#define SW2 PORTAbits.RA2 | |
#define BUZ PORTAbits.RA4 | |
#define RLY PORTAbits.RA5 | |
#define H_ADD 0x00 | |
#define M_ADD 0x01 | |
#define S_ADD 0x02 | |
__EEPROM_DATA(0,0,50,0,0,0,0,0); | |
void variableInit(void); | |
void systemTicks(void); | |
void loadSetting(void); | |
void timeSet(void); | |
void SSD(unsigned short long secs); | |
void systemInit(void); | |
void timerInit(void); | |
void timerRun(void); | |
void timeSave(unsigned short long secs); | |
unsigned short long timeLoad(void); | |
unsigned char eeRead(unsigned char add); | |
void eeCommand(void); | |
void eeWrite(unsigned char add,unsigned char dat); | |
unsigned short timeRead(void); | |
bit hrs_on(void); | |
bit mns_on(void); | |
unsigned short long secSet; | |
unsigned short cnt0,cnt1,cnt2,cnt3; | |
unsigned char swCnt; | |
unsigned char sysTick; | |
bit _L,load,_timerRun; | |
void interrupt T0_ISR(void){ | |
if(INTCONbits.TMR0IF){ | |
sysTick++; | |
} | |
INTCONbits.TMR0IF=0; | |
} | |
void main(){ | |
__delay_ms(100); | |
systemInit(); | |
variableInit(); | |
while(1){ | |
systemTicks(); | |
if(_timerRun==1) | |
timerRun(); | |
else | |
timerInit(); | |
SSD(secSet); | |
} | |
} | |
void timerInit(void){ | |
if(SW2==0){ | |
if(cnt2>=150){ | |
load^=1; | |
swCnt=2; | |
cnt2=0; | |
} | |
} | |
if(load==0) timeSet(); | |
else loadSetting(); | |
} | |
void timerRun(void){ | |
unsigned short long temp; | |
static unsigned long buzTimer=0; | |
temp=secSet; | |
if(cnt3>=oneSec) { | |
secSet--; | |
RLY=1; | |
buzTimer++; | |
if(buzTimer<=3) BUZ=0; | |
else BUZ=1; | |
if(secSet<=3) BUZ=0; | |
cnt3=0; | |
} | |
if(secSet==0){ | |
RLY=0; | |
_timerRun=0; | |
secSet=temp; | |
BUZ=1; | |
swCnt=2; | |
} | |
} | |
void SSD(unsigned short long secs){ | |
// Common Cathode | |
const char SEG[10]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F} ; | |
unsigned short long hrs,mns,scs; | |
if(cnt1>=oneSec){ | |
_L^=1; | |
cnt1=0; | |
} | |
hrs=secs/3600; | |
mns=(secs%3600)/60; | |
scs=secs%60; | |
switch(cnt0){ | |
case 1 : | |
PORTC=0x00; | |
PORTB=SEG[hrs/10]; | |
if(hrs_on()==1) | |
PORTC=0x01; | |
else PORTC=0; | |
break; | |
case 2 : | |
PORTC=0x00; | |
PORTB=SEG[hrs%10]; | |
if(hrs_on()==1) | |
PORTC=0x02; | |
else PORTC=0; | |
break; | |
case 3 : | |
PORTC=0x00; | |
if(_L==1){ | |
PORTB=0x40; | |
if(hrs_on()==1) | |
PORTC=0x04; | |
else PORTC=0x00; | |
} | |
break; | |
case 4 : | |
PORTC=0x00; | |
PORTB=SEG[mns/10]; | |
if(mns_on()==1) | |
PORTC=0x08; | |
else PORTC=0x00; | |
break; | |
case 5 : | |
PORTC=0x00; | |
PORTB=SEG[mns%10]; | |
if(mns_on()==1) | |
PORTC=0x10; | |
else PORTC=0x00; | |
break; | |
case 6 : | |
PORTC=0x00; | |
if(_L==1){ | |
PORTB=0x40; | |
if(mns_on()==1) | |
PORTC=0x20; | |
else PORTC=0x00; | |
} | |
break; | |
case 7 : | |
PORTC=0x00; | |
PORTB=SEG[scs/10]; | |
PORTC=0x40; | |
break; | |
case 8 : | |
PORTC=0x00; | |
PORTB=SEG[scs%10]; | |
PORTC=0x80; | |
break; | |
case 9 : | |
PORTC=0x00; | |
cnt0=0; | |
break; | |
} | |
} | |
void systemInit(){ | |
TRISA=0x0F; | |
TRISB=0x00; | |
TRISC=0x00; | |
// Analog Config | |
ADCON1bits.PCFG=14; // RA0 Analog | |
ADCON1bits.ADFM=1; // right justified | |
ADCON1bits.ADCS2=0; // adc osc | |
ADCON0bits.ADCS=0x03; // int RC | |
ADCON0bits.CHS=0x00; // AN0 | |
ADCON0bits.ADON=1; // ADC ON | |
// Timer Config | |
OPTION_REGbits.T0CS=0; // SELECT FOSC | |
OPTION_REGbits.PSA=0; // TMR0 Prescaler | |
OPTION_REGbits.PS=0x04; // 1:32 | |
// Interrupt Config | |
INTCONbits.GIE=1; | |
INTCONbits.TMR0IE=1; | |
INTCONbits.TMR0IF=0; | |
} | |
void variableInit(void){ | |
// Variable Initialize | |
cnt0=0; | |
cnt1=0; | |
cnt2=0; | |
cnt3=0; | |
load=0; | |
_L=0; | |
_timerRun=0; | |
swCnt=0; | |
sysTick=0; | |
BUZ=1; | |
RLY=0; | |
} | |
unsigned short timeRead(void){ | |
unsigned int RL,RH; | |
unsigned int adResult; | |
unsigned short timeVal; | |
ADCON0bits.GO_nDONE=1; | |
while(ADCON0bits.GO_nDONE); | |
RL=ADRESL; | |
RH=ADRESH; | |
adResult=(RH*256)+RL; | |
timeVal=(adResult*59)/1023; | |
return timeVal; | |
} | |
void loadSetting(void){ | |
secSet=timeLoad(); | |
if(SW1==0&&cnt2>100){ | |
_L=0; | |
_timerRun=1; | |
cnt2=0; | |
} | |
} | |
void timeSet(void){ | |
unsigned short long timeVal; | |
static unsigned short long hms[3]={0,0,0}; | |
if(sysTick==1) | |
timeVal=timeRead(); | |
hms[0]=timeVal; | |
if(swCnt==3){ | |
if(secSet!=0) { | |
_timerRun=1; | |
timeSave(secSet); | |
} | |
GIE=1; | |
TMR0IE=1; | |
TMR0IF=0; | |
swCnt=0; | |
} | |
if((SW1==0)&&(cnt2>100)){ | |
swCnt++; | |
if(swCnt=2){ | |
cnt0++; | |
cnt1++; | |
cnt2++; | |
cnt3++; | |
sysTick=0; | |
} | |
} | |
bit hrs_on(void){ | |
char state; | |
if(load==0&&_timerRun==0) | |
if(swCnt>1) | |
state=1; | |
else state=0; | |
return state; | |
} | |
bit mns_on(void){ | |
char state; | |
if(load==0&&_timerRun==0) | |
if(swCnt>0) | |
state=1; | |
else state=0; | |
return state; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* File: config.h | |
* Author: BongTha | |
* | |
* Created on September 21, 2018, 8:35 PM | |
*/ | |
#ifndef CONFIG_H | |
#define CONFIG_H | |
#ifdef __cplusplus | |
extern "C" { | |
#endif | |
#ifdef __cplusplus | |
} | |
#endif | |
#endif /* CONFIG_H */ | |
// PIC16F876A Configuration Bit Settings | |
// CONFIG | |
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator) | |
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled) | |
#pragma config PWRTE = ON // Power-up Timer Enable bit (PWRT disabled) | |
#pragma config BOREN = ON // Brown-out Reset Enable bit (BOR enabled) | |
#pragma config LVP = OFF // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming) | |
#pragma config CPD = OFF // Data EEPROM Memory Code Protection bit | |
#pragma config WRT = OFF // Flash Program Memory Write Enable bits | |
#pragma config CP = OFF // Flash Program Memory Code Protection bit |
I shared this project on the PCB fab service website.

The pictures below show a sample pre-production PCB created by the Gerber viewer.
![]() |
Components Side |
![]() |
Copper Soldering Side |
No comments:
Post a Comment