A 4 Channel PIC controlled Light Sequencer

This article describes the schematic and PIC code of a versatile 4-channel sequencing light controller that has been designed for future expansion by means of a plug-in module, for functions such as sound-to-light.

The PIC code for the controller includes some basic functions for debouncing and handling button presses, communicating events via a queue, and generating time-sequenced outputs, which may be of interest to the student in many other applications.

INTRODUCTION

The design for the light sequencer described here was started in 2008, and finished in 2009. Its schematic capture was done using my own software, CCTScribe, and the PCB was laid out using an exported netlist in conjunction with FreePCB (version 1.353) by Allan Wright.

Schematic

You can read the schematic as an image file by following this link, preferably opening it in a new window or tab so that you can refer to it while following the text here.

The Light Sequencer in Action - Christmas 2012


Schematic Description

The power supply for the sequencer uses a conventional (50Hz) mains isolation transformer and half-wave rectifier arrangement, feeding two linear regulators for 12V and 5V DC supplies. The mains supply to the transformer is filtered by a common-mode choke CH1, an approved X-class safety capacitor C6, and approved Y-class safety capacitors C7 and C8, in order to suppress RF noise emissions from the processor and triac switching. There is also a ferrite sleeve on the mains lead to add further common-mode noise filtering. A metal-oxide varistor MOV2 protects the sequencer from transient high voltage spikes on the mains.

The unit was constructed within a metal enclosure, which is earthed to the mains safety earth via a secure earth point bolted to the chassis with a star washer to ensure good contact to bare metal (which is clear of any paint or other insulation), and a shake-proof washer to prevent the nut and bolt coming undone in any likely handling conditions.

A PIC 16F628 microcontroller controls switching on and off of the 4 triac channels. The schematic mentions the 16F627, but this had to be changed to the 16F628 because there was not enough code space. The software allows several light sequencing patterns to be selected via the 5 push switches on the front panel. These push switches connect MCU inputs to 5V and have pull-down resistors to ensure a low level when the switch is released.

Some of the MCU I/O lines and the DC power supplies are connected to expansion headers, to allow for things like sound-to-light processing in future.

Each triac channel is opto-isolated from the low voltage control circuitry. This ensures that the mains voltage present on the switched power channels can not cause a shock hazard via this route, or expose the sensitive MCU circuitry to voltage spikes on the mains or voltage differentials between neutral and earth. In addition to driving each opto-isolator, switching transistors (BC548C) also drive a LED in series with the opto-isolator diode to show when the triac channel is on.

The control circuitry common rail is connected to safety earth, so that in the event of a short circuit from mains voltage, the protective fuse should blow and prevent any control wiring from reaching hazardous voltage levels. This also takes into consideration that future expansion might take some of the control functions outside of the main unit e.g. into a control box. The fuse for the sequencer power (in the mains plug) should be kept to a low rating; the sequencer itself only takes a few Watts at most hence a 1A fuse in the mains plug would be sufficient (equipment rating <= 240 Watts), but commonly available fuses may be limited to 2 or 3A. The triac circuits have their own fusing, per channel.

A metal-oxide varistor MOV1/3/4/5 protects each triac channel from transient high voltage spikes on the mains, which is connected externally in series with the lighting load. The opto-isolator output transistor drives the triac gate and the gate circuit is protected from overcurrent by a positive temperature coefficient thermistor.

An RC (resistor-capacitor) snubber network is connected across the triac. This suppresses rapid voltage changes due to transients or inductive loads which can retrigger the triac. A rapid dV/dt across the triac junction capacitance can cause a current to flow, proportional to dV/dt, that can turn the triac on if sufficiently large. A small amount of inductance is inevitable in wiring loops, even with incandescent lamps, and with more advanced lighting the reactive nature of the load depends heavily on the individual system design.

Each triac channel is controlled by a triac rated at 8A RMS, 80A non-repetitive peak surge, and is protected by an 8A anti-surge fuse. An 8A load should be considered an absolute limit, and if you load a channel with incandescent lamps consuming 8A at normal mains voltage, the lower resistance of the filaments when the lamps have just been turned on in a sequence may well blow the fuse - therefore it is recommended that each channel should be limited to 4A of current load, i.e. 1kW total load. In normal use I wouldn't expect to be driving that much load - working well below maximum ratings makes for good long-term reliability. It is also worth noting that the cold resistance of a tungsten filament incandescent lamp is about 1/15 of its hot resistance when fully warmed up, therefore since P = V^2/R, a 100W lamp will take an initial power draw of about 1500W at turn-on, which supposedly is catered for by the peak surge rating of the triac, but depends on the dynamic characteristic of the power level.

By using an internal ceramic pad, the BTA series provides a voltage insulated tab (rated at 2500V RMS) complying with UL standards (File ref.: E81734). This also handily solves the problem of providing suitable thermal and electrical insulation for the devices, which are mounted directly onto the earthed metal enclosure.

Take Heed: Important Safety Considerations

In the days before semiconductor technology became generally used for most applications, electronic equipment using valves was frequently built by the home constructor to run on mains power, or high-tension batteries (90 volts or more, which is considered hazardous by the standards of the Low Voltage Directive), and used internal DC voltage rails commonly from 150V to 350V. Books and magazine articles frequently gave advice on safe practice for constructors. The threat to personal safety from the high voltages present within equipment was always in your mind, especially when working on unprotected equipment on the bench. Today, it is commonplace for electronic equipment to be powered from a low voltage source, independently provided in the form of a commercial, sealed power unit. People working with electronic circuits may do so for a considerable time without ever coming across hazardous voltages, or indeed gaining the knowledge and experience necessary to work safely where they are present. Therefore, it is even more important that the safety aspects of working with systems that incorporate hazardous voltage levels should be emphasized.

Anyone considering laying out a PCB for a project like this should check that insulation, clearance, and creepage are in accordance with the IEC 60950 standard.

This article is primarily for your information and education. A project of this nature includes circuits operating at mains voltage, which is potentially hazardous. Electricity can kill or cause serious injury. Proper care must always be taken during construction, testing and use. Therefore, only suitably qualified persons should attempt this, who are fully knowledgeable about the work to be done, are aware of the hazards involved and the need to take appropriate measures to prevent harm occurring to anybody as a result of the hazards, who have acquired the required knowledge by reason of education, training, experience and a combination of those factors. For more information, please see this article by Stewart Watkiss (by no means an exhaustive treatment of the subject, but a useful introduction): Keeping yourself and others safe when designing and making electronic projects

See also my general Disclaimer

Expansion Interface

The expansion interface consists of headers positioned on the main PCB so that a daughter board, as yet unspecified, can be added in future.

The interface provides 12V and 5V DC power via PL9, a 4 way header. Two 10-way headers provide connections to I/O signals. These include:

  • The 4 digital control outputs to the triac hardware
  • The 5 pushbuttons on the front panel
  • RA5/MCLR/VPP
  • MCU VDD
  • RB6 and RB7 via 1k resistors to buffer the ICSP interface if these ports are shared
  • Spare I/O ports RB4 and RB5

The PIC Code

The code was written for the HI-TECH Software PICC Lite compiler, version 9.65, running in the Microchip MPLAB IDE v7.30. This requires the file htc.h to be included at the head of the program, which imports the relevant headers containing the processor specific definitions.

In this family of PIC processors, the PIC16F627 has 1024 bytes of program memory, and the PIC16F628 has 2048 bytes. Both have 224 bytes of RAM and 128 bytes of EEPROM.

A Velleman K8048 kit was used for initial experimentation and code development, as this enabled working code to be produced without needing live triac circuits.

The code begins with basic definitions and macros. We define a Boolean type, which is simply a byte value that can be a 0 or a 1, with the names FALSE and TRUE made available to make it clear that boolean values are being used. Bitfields can also be used as Boolean values, but using a whole byte is just easier when there is no need to pack data into individual bits.

The unsigned 8-bit BYTE is also a very common definition (some programmers prefer a definition that clarifies the word width, e.g. uint8).

The port reading macros RA0_HI etc. provide an abbreviated way of accessing each relevant I/O bit on the MCU ports, and should result in more readable code.

The Configuration Bits customize certain aspects of the MCU device, via the configuration word, which is usually set up with the aid of the compiler by using a special instruction such as "__CONFIG".

  • _BODEN_ON: Turn on power brown-out reset.
  • _CP_OFF: Turn off program code protection.
  • _DATA_CP_OFF: Turn off data memory code protection.
  • _PWRTE_ON: Turn on the power-up timer.
  • _WDT_OFF: Turn off the watchdog timer.
  • _LVP_OFF: Disable low-voltage in-circuit programming.
  • _MCLRE_ON: Configures RA5/MCLRpin function as external MCLR reset.
  • _XT_OSC: Specify that the device is using an XT oscillator (for 4 MHz operation).
  • _HS_OSC: Specify that the device is using the high speed oscillator (for 20 MHz operation).

The code can be configured for a 4 MHz crystal or a 20 MHz crystal by changing the definition of XTAL accordingly. This affects not only the configuration bits, but also some timing values.


#include <htc.h>

#ifndef BOOL
	#define BOOL char
	#define FALSE 0
	#define TRUE 1
#endif

#define BYTE unsigned char

/* Port bit reading macros 
   Give a non-zero value if the port input is high */
#define RA0_HI (PORTA&0x01)
#define RA1_HI (PORTA&0x02)
#define RA2_HI (PORTA&0x04)
#define RA3_HI (PORTA&0x08)
#define RA4_HI (PORTA&0x10)
#define RA5_HI (PORTA&0x20)
#define RA6_HI (PORTA&0x40)
#define RA7_HI (PORTA&0x80)
#define RB0_HI (PORTB&0x01)
#define RB1_HI (PORTB&0x02)
#define RB2_HI (PORTB&0x04)
#define RB3_HI (PORTB&0x08)
#define RB4_HI (PORTB&0x10)
#define RB5_HI (PORTB&0x20)
#define RB6_HI (PORTB&0x40)
#define RB7_HI (PORTB&0x80)

// Configuration Bits
#define _BODEN_ON                    0x3FFF
#define _BODEN_OFF                   0x3FBF
#define _CP_ALL                      0x03FF
#define _CP_75                       0x17FF
#define _CP_50                       0x2BFF
#define _CP_OFF                      0x3FFF
#define _DATA_CP_ON                  0x3EFF
#define _DATA_CP_OFF                 0x3FFF
#define _PWRTE_OFF                   0x3FFF
#define _PWRTE_ON                    0x3FF7
#define _WDT_ON                      0x3FFF
#define _WDT_OFF                     0x3FFB
#define _LVP_ON                      0x3FFF
#define _LVP_OFF                     0x3F7F
#define _MCLRE_ON                    0x3FFF
#define _MCLRE_OFF                   0x3FDF
#define _ER_OSC_CLKOUT               0x3FFF
#define _ER_OSC_NOCLKOUT             0x3FFE
#define _INTRC_OSC_CLKOUT            0x3FFD
#define _INTRC_OSC_NOCLKOUT          0x3FFC
#define _EXTCLK_OSC                  0x3FEF
#define _LP_OSC                      0x3FEC
#define _XT_OSC                      0x3FED
#define _HS_OSC                      0x3FEE

// Crystal frequency in use
#define XTAL 20

#if XTAL == 4
/* Config for 4 MHz XTAL (e.g. Velleman board) */
__CONFIG(_BODEN_ON & _CP_OFF & _DATA_CP_OFF & _PWRTE_ON & _WDT_OFF & _LVP_OFF & _MCLRE_ON & _XT_OSC);
#endif

#if XTAL == 20
/* Config for 20 MHz XTAL (e.g. custom board) */
__CONFIG(_BODEN_ON & _CP_OFF & _DATA_CP_OFF & _PWRTE_ON & _WDT_OFF & _LVP_OFF & _MCLRE_ON & _HS_OSC);
#endif

Variables and additional definitions for the code follow the configuration bit setup. An array of values, bitmask[], is used to access a specified bit in a byte by mapping the array index to a power-of-2 value, which can then be used to select an individual bit by logically combining the element value with the byte. Thus, index 1 refers to the element bitmask[1], which has the value 2^1 = 2 = 00000010 B. A logical AND with a byte gives a result that is non-zero only if bit 1 is non-zero. A logical OR with a byte, written back into the byte, has the effect of setting bit 1. If the mask value is inverted, then a logical AND with the byte has the effect of clearing bit 1.

The pattern arrays contain values that are used to control individual light channels. Each bit of the 4 LS bits corresponds to one channel. Thus, each element of the pattern array controls a different setting of the light channels. Looking at the sequential scan pattern, you can see that each bit is set in turn, so that one light channel will be turned on individually at a time, moving from one output to the next as the next array value is used. As the pattern arrays are of different lengths, there is an associated count to be used by the code.

The variable ppattern is a pointer to a BYTE. This will be set to point to the selected pattern array.


// Bit masks for up to 5 switches
unsigned char bitmask[5]={1,2,4,8,16};

#define MAXMODES 7
#define MAXSPEEDS 4

// Pattern arrays for outputs
// all off
BYTE pattern0[1]={0x00};
BYTE n0 = 1;
// sequential scan
BYTE pattern1[4]={0x01,0x02,0x04,0x08};
BYTE n1 = 4;
// sequential scan and reverse
BYTE pattern2[6]={0x01,0x02,0x04,0x08,0x04,0x02};
BYTE n2 = 6;
// Bargraph
BYTE pattern3[8]={0x00,0x01,0x03,0x07,0x0F,0x07,0x03,0x01};
BYTE n3 = 8;
// Alternating pair (adjacent on)
BYTE pattern4[2]={0x03,0x0C};
BYTE n4 = 2;
// Alternating pair (interleaved on)
BYTE pattern5[2]={0x05,0x0A};
BYTE n5 = 2;

BYTE * ppattern = pattern0;
BYTE npattern = 1;	// init to same as n0

Timing values depend on the value of the crystal being used:


#if XTAL == 4
// delay (s)	count (256 us multiplier)
//	1			3906
// 0.5			1953
// 0.25			977
// 0.125		488
unsigned short int speed_init[4] = {3906, 1953, 977, 488};
unsigned short int speed = 0;			// speed selector
unsigned short int count_t0 = 3906;		// 1 second (256 us multiplier)
unsigned short int count_t_2ms = 8;		// 2.048 millisecond (256 us multiplier)
#endif

#if XTAL == 20
// delay (s)	count (51.2 us multiplier)
//	1			19531
// 0.5			9766
// 0.25			4883
// 0.125		2441
unsigned short int speed_init[4] = {19531, 9766, 4883, 2441};
unsigned short int speed = 0;			// speed selector
unsigned short int count_t0 = 19531;	// 1 second (51.2 us multiplier)
unsigned short int count_t_2ms = 40;	// 2.048 millisecond (51.2 us multiplier)
#endif

Timing is done with the help of a fixed interrupt timing interval, and a counting algorithm that counts down successive interrupts until a desired multiple of that timing interval has been reached. Then the t0cntflg or t_2ms_cntflg flag gets set. The t0cntflg flag controls the timing of light pattern steps, and the t_2ms_cntflg flag is used to sample and deglitch the switch inputs at 2ms intervals.


BOOL t0cntflg = FALSE;
BOOL t_2ms_cntflg = FALSE;

typedef enum tag_SW_EVENT_TYPE
{
	SW_NULL,
	SW_LO_HI,
	SW_HI_LO,
} SW_EVENT_TYPE;

// Number of switches
#define NSW 5
unsigned short int sw_counts[NSW];	// Deglitching counts for switches
unsigned BOOL sw_input_last[NSW];	// Raw switch input
unsigned BOOL sw_input_new[NSW];	// Raw switch input
unsigned BOOL sw_states[NSW];		// Deglitched switch states

// Number of events in event queue
#define NQ 5
BYTE event_q[NQ];	// Event queue
BYTE event_q_head;	// Event queue pointer
BYTE event_q_tail;	// Event queue pointer

int mode = 0;	// set effect mode
int mstep = 0;	// set effect step

BYTE _tcyc = 0;

BYTE newPortB;

unsigned short int data;

One interrupt vector handles all interrupts, so the interrupt service routine must check the interrupt flags to see which source caused the interrupt.


//
// If XTAL == 4 MHz:
// Input to Timer 0 module is Fosc / 4  = 1 MHz = 1.0 us clock period
// After each 8 bit timer counter overflow, 
// this interrupt is called every 256 clock periods, i.e. 256 us
// (when the prescaler is assigned to the WDT in the OPTION register)
//
// If XTAL == 20 MHz:
// Input to Timer 0 module is Fosc / 4  = 5 MHz = 0.2 us clock period
// After each 8 bit timer counter overflow, 
// this interrupt is called every 256 clock periods, i.e. 51.2 us
// (when the prescaler is assigned to the WDT in the OPTION register)
static void interrupt
isr(void)			// Interrupt function - the name is unimportant.
{
	if (T0IF) 
	{				// timer 0 interrupt
// Update second counter (used for output timing)
		count_t0--;
		if (count_t0==0)
		{
			t0cntflg = TRUE;		// Set flag to show timer 0 countdown has expired

			count_t0 = speed_init[speed];		// re-init to countdown value
		}

// Update millisecond counter (used for switch deglitching)
		count_t_2ms--;
		if (count_t_2ms==0)
		{
			t_2ms_cntflg = TRUE;		// Set flag to show timer countdown has expired
#if XTAL == 4
			count_t_2ms = 8;		// re-init to countdown value
#endif

#if XTAL == 20
			count_t_2ms = 40;		// re-init to countdown value
#endif
		}

		T0IF = 0;			// clear the interrupt flag
	}	// if (T0IF)...

}

Button presses are handled as queued events, so the following functions are provided to implement the queue handling:


// Returns the next pointer to the queue
// allowing for wrap around from location NQ-1 to location 0
BYTE GetNextQueuePtr(BYTE icurrptr)
{
	BYTE inextptr;

	inextptr = icurrptr+1;
	if (inextptr > (NQ-1) )
	{
		inextptr = 0;
	}
	return (inextptr);
}

// Adds an event to a Queue 
// Rules of the queue:
// The queue is an array of NQ bytes.
// New events are added at the location pointed to by the Head pointer.
// Existing events in the queue are removed first-in-first-out
// at the location pointed to by the Tail pointer.
// The queue is empty when Head and Tail pointers point to the same location.
// The queue is full when the Tail pointer is one location ahead of the head pointer
// The queue wraps around from location NQ-1 to location 0
// The maximum capacity of the queue is NQ-1 events
// Returns TRUE if the event was added
// Returns FALSE if the event was not added because the queue is full
// CJS 23/12/09
BOOL Add_To_Queue(BYTE event)
{
	BYTE inextptr;
	BOOL bRet;

	inextptr = GetNextQueuePtr(event_q_head);	// Inc event queue pointer

// Check if the queue is full
	if (inextptr == event_q_tail)
	{
// Queue is full
		bRet = FALSE;
	}
	else
	{
		event_q[event_q_head] = event;	// Add event at head of queue
		event_q_head = inextptr;		// Update event queue pointer
		bRet = TRUE;
	}
	return (bRet);
}

// Gets an event from the queue
// Returns TRUE if the event was read
// Returns FALSE if the event was not read because the queue is empty
// CJS 23/12/09
BOOL Get_From_Queue(BYTE * pEvent)
{
	BYTE event;
	BOOL bRet;

// Check if an event is in the queue
	if (event_q_head == event_q_tail)
	{
// Queue is empty
		event = 0;	// null event
		bRet = FALSE;
	}
	else
	{
		event = event_q[event_q_tail];	// Remove event from tail of queue
		event_q_tail = GetNextQueuePtr(event_q_tail);	// Inc event queue pointer
		bRet = TRUE;
	}

	*pEvent = event;	// Return event via pointer
	return (bRet);		// return status
}

In random mode, the following function is used to generate pseudo-random values using a linear-feedback shift register method:


// initval is a value > 0 to initialise the random value (the init value is returned)
// e.g. 0x5A0B
// then to get each updated random value, call the function with 0 as the parameter.
unsigned short LFSR16(unsigned short initval)
{
	static unsigned short lfsr;
	unsigned short lsb;

	if (initval > 0)
	{
		lfsr = initval;
	}
	else
	{
		lsb = lfsr & 1;		// Get lsb
		lfsr >>= 1;			// Shift the register 1 bit right
		if (lsb==1)
		{	
// Apply toggle mask if the lsb is 1
// Taps at bits 16, 14, 13, 11 = characteristic polynomial x^16 + x^14 + x^13 + x^11 + 1
			lfsr ^= 0xB400u;
		}
	}

	return (lfsr);

}

The main program is called by the startup code that is included by the compiler when the microcontroller is powered up or reset, and after initialisation code, operates in an endless loop, within the while(1) statement's curly brackets.

The MCU is set up with the prescaler assigned to the watchdog timer and set for a 1/128 rate (the slowest). This results in an effective 1:1 prescaler ratio for timer 0. Port A bits are set for input (the front panel switches use 5 of these bits), and Port B bits are set for output (four of these bits control the triac circuits). The arrays used for reading and deglitching the switch inputs are initialised, the array and head/tail pointer variables used for the event queue are initialised, the pseudo-random number generator is initialised, and timer 0 is initialised to count at the crystal oscillator frequency / 4, i.e. for a 20 MHz crystal, at 5 MHz, and to interrupt when the timer count overflows after 256 clock periods, i.e. 51.2 microseconds. The speed_init[] array stores four counts for the timer 0 interrupt, selected by the speed variable, so that the t0cntflg flag can be set to update light pattern steps at the 4 different preset rates of 1, 0.5, 0.25 or 0.125 seconds. Then just before entering the while(1) loop, interrupts are enabled.

The while(1) loop carries out a series of checks every time around the loop. If the 2 ms timer flag has been set, the switch inputs are read and stored as new values. Commonly, switch bounce ends within 20ms, so if the current switch input has remained the same value for 20ms, the code checks if the switch state has changed and updates the switch state. If the switch state changed, an event is added to the event queue and the deglitching count is cleared. The event contains data to show if the switch changed from low to high, or from high to low, and the switch number.

Using an event queue for inputs enables software to deal with input changes in an orderly manner so that input events can be handled when the rest of the software is not busy with something else and facilitates the writing of modular software that can easily be updated, e.g. if an add-on card is installed which requires the handling of additional inputs and outputs. However for a simple project such as sequencing alone, simply setting a flag bit or BOOL variable to indicate that a button had been pressed or released, and clearing the flag when the event was handled, is probably sufficient and would result in a smaller code footprint.

Next, the main loop gets the next event from the queue - the status returned by Get_From_Queue() indicates whether there was an event available - and checks the switch number. For each switch number, there can be a different action which depends on whether the switch state went from low to high (pressed), or from high to low (released). Switch 4 changes the pattern mode, which selects one of the light patterns (mode 6 is a special case which enables random outputs, when the pattern arrays are not used). Switches 0, 1, 2 and 3 are light override switches in mode 0 giving the operator direct control of the light output channels. If a button release event occurs, the relevant output bit is cleared. In other modes, switches 0 and 1 adjust the light pattern update speed. A button press on switch 0 selects the next lower speed and a button press on switch 1 selects the next higher speed.

Next, the main loop animates the outputs according to the current mode setting. If the flag t0cntflg is set, it is time to change the outputs, and this is done by selecting the next bit pattern in the pattern array pointed to by the pointer ppattern. If the mode is set for random output, a random word is generated and the lowest 4 bits of that word are used to set the output bits. Then the pattern step variable mstep is incremented to change the pattern for the next time around the loop - this is post-incremented, so that the pattern starts with array element 0. The flag t0cntflg must be cleared after processing the output update so that the outputs will only change after the timer 0 interrupt sets the flag after the proper delay.

Finally, the override state is checked for mode 0, which sets the relevant output bit if the current switch state is pressed.


void main(void)
{
	int i, ir;
	short int j, del;
	long int l;
	unsigned int uidc;

	BOOL bStatus;	// Status for queue functions
	BYTE event_out;
	BYTE event_type;
	BYTE event_switch;

/* setup stuff */

// CMCON is in data memory bank 0
	CMCON = 0b00000111;	// Disable Comparator modules (CM2:CM0 = 0b111, comparators off)
// INTCON is in data memory bank 0
	INTCON = 0;			// Init all interrupts as disabled
/* By default, port A pins are set to be inputs to the analog to digital converters, 
	not digital I/O. See ADCON register */

/*
bit 7 RBPU: PORTB Pull-up Enable bit 
	1 = PORTB pull-ups are disabled
	0 = PORTB pull-ups are enabled by individual port latch values
bit 6 INTEDG: Interrupt Edge Select bit
	1 = Interrupt on rising edge of RB0/INT pin
	0 = Interrupt on falling edge of RB0/INT pin
bit 5 T0CS: TMR0 Clock Source Select bit
	1 = Transition on RA4/T0CKI pin
	0 = Internal instruction cycle clock (CLKOUT)
bit 4 T0SE: TMR0 Source Edge Select bit
	1 = Increment on high-to-low transition on RA4/T0CKI pin
	0 = Increment on low-to-high transition on RA4/T0CKI pin
bit 3 PSA: Prescaler Assignment bit
	1 = Prescaler is assigned to the WDT
	0 = Prescaler is assigned to the Timer0 module
bit 2-0 PS2:PS0: Prescaler Rate Select bits
Bits	TMR0 rate	WDT rate
000		1 : 2		1 : 1
001		1 : 4		1 : 2
010		1 : 8		1 : 4
011		1 : 16		1 : 8
100		1 : 32		1 : 16
101		1 : 64		1 : 32
110		1 : 128		1 : 64
111		1 : 256		1 : 128

*/
// OPTION is in data memory bank 1
// Note: To achieve a 1:1 prescaler assignment for TMR0, assign the prescaler to the WDT
// (PSA = bit 3 = 1).
	OPTION	= 0b00001111;

// TRISA is in data memory bank 1
// Note: RA4 is open drain on PIC16F62X
// RA6 and RA7 are used as crystal oscillator pins, RA5 is used as /MCLR
	PORTA = 0x00;	// Clear port A
	TRISA = 0xFF;	// all set to input
	PORTB = 0x00;	// Clear port B
// TRISB is in data memory bank 1
// RB6 and 7 are reserved for in-circuit prog.  When running, port impedance 
// swamps programmer source resistance ( > 1k ohms )
	TRISB = 0x00;	// RB7...RB0 are outputs.  

// Init variables
	for (i=0; i<NSW; i++)
	{
		sw_counts[i] = 0;		// Init deglitching counts for switches
		sw_input_last[i] = FALSE;	// Init raw switch input
		sw_input_new[i] = FALSE;	// Init raw switch input
		sw_states[i] = FALSE;	// Init deglitched states for switches
	}

	for (i=0; i<NQ; i++)
	{
		event_q[i] = SW_NULL;
	}
	event_q_head = 0;	// Event queue pointer
	event_q_tail = 0;	// Event queue pointer

	data = LFSR16(0x5A0B);		// Init 16 bit PRBS generator

/* Set up timer 0 (assumes PSA set to 0, and prescaler set to 256 in OPTION) 
   to generate regular interrupts
   Input to prescaler = Fosc / 4
*/
	T0CS = 0;			// select internal clock
	T0IF = 0;			// Clear timer int flag
	TMR0 = 0;			// Clear timer register (counts 256)
	T0IE = 1;			// Enable TMR0 overflow interrupt
/* End of timer 0 setup */

	GIE = 1;			// Set INTCON Global enable interrupts bit
	PEIE = 1;			// Enable all unmasked peripheral interrupts

	while(1)
	{
// Read switches every 2 ms and deglitch
		if (t_2ms_cntflg)
		{
// Derive boolean switch values from masked port bits
			if (RA0_HI != 0)
			{
				sw_input_new[0] = TRUE;
			}
			else
			{
				sw_input_new[0] = FALSE;
			}
			if (RA1_HI != 0)
			{
				sw_input_new[1] = TRUE;
			}
			else
			{
				sw_input_new[1] = FALSE;
			}
			if (RA2_HI != 0)
			{
				sw_input_new[2] = TRUE;
			}
			else
			{
				sw_input_new[2] = FALSE;
			}
			if (RA3_HI != 0)
			{
				sw_input_new[3] = TRUE;
			}
			else
			{
				sw_input_new[3] = FALSE;
			}
// Note: An extra switch must be added to the Velleman K8048, connected to pin 3
// of the 18 pin socket, to support the switch on RA4 on 16F627 and 16F628 PICs
			if (RA4_HI != 0)
			{
				sw_input_new[4] = TRUE;
			}
			else
			{
				sw_input_new[4] = FALSE;
			}

			for (i=0; i<NSW; i++)
			{
				if (sw_input_new[i] == sw_input_last[i])
				{
					sw_counts[i] += 2;	// Increment the deglitching count (units of 1 ms)
				}
				else
				{
					sw_counts[i] = 0;	// Reset the deglitching count
				}
// If input has been same value for 20ms, update the deglitched switch state
				if (sw_counts[i] > 20)
				{
// Has switch state changed?
					if (sw_states[i] != sw_input_new[i])
					{	// Low-high or high-low?
						if (sw_input_new[i] == TRUE)
						{
// Event byte = type of event in MS 4 bits, switch number in LS 4 bits
							bStatus = Add_To_Queue((SW_LO_HI<<4) + i);
						}
						else
						{
// Event byte = type of event in MS 4 bits, switch number in LS 4 bits
							bStatus = Add_To_Queue((SW_HI_LO<<4) + i);
						}
					}
					sw_states[i] = sw_input_new[i];
					sw_counts[i] = 0;	// Reset the deglitching count
				}
			sw_input_last[i] = sw_input_new[i];
			}
			t_2ms_cntflg = FALSE;
		} // if (t_2ms_cntflg)

		newPortB = PORTB;	// until modified

		bStatus = Get_From_Queue(&event_out);
		event_type = event_out>>4;
		event_switch = event_out&0x0f;
		if (bStatus)
		{
// Switch 4 changes the mode
			if (event_switch==4)
			{	
				if (event_type == SW_LO_HI)
				{
					mode++;		// Next pattern
					if (mode>(MAXMODES-1) )
					{
						mode = 0;	// wrap
					}
					PORTB = 0;	// clear old pattern
					mstep = 0;	// clear old step
					switch (mode)
					{
						case 0:
							ppattern = &pattern0;
							npattern = n0;
							break;
						case 1:
							ppattern = &pattern1;
							npattern = n1;
							break;
						case 2:
							ppattern = &pattern2;
							npattern = n2;
							break;
						case 3:
							ppattern = &pattern3;
							npattern = n3;
							break;
						case 4:
							ppattern = &pattern4;
							npattern = n4;
							break;
						case 5:
							ppattern = &pattern5;
							npattern = n5;
							break;
						case 6:
							// random mode - pattern not used and set to default.
							ppattern = &pattern0;
							npattern = n0;
							break;
					}	// switch (mode)
				}	// if (event_type == SW_LO_HI)
			}	// if (event_switch==4)
			else if (event_switch<=3)
			{	
				if (mode==0)
				{
// Override when switch turns off (first 4 switches in mode 0)
// This is necessary to avoid delay turning output off as override from state of 
// switch is only active when switch is on.
					if (event_type == SW_HI_LO)
					{	// Clear the switch-controlled bit
						newPortB &= ~bitmask[event_switch];
					}
				}
				else
				{
// In modes != 0, switch 0 and 1 adjust the speed
					if (event_switch==0)
					{	
						if (event_type == SW_LO_HI)
						{
							if (speed==0)
							{
								speed = (MAXSPEEDS-1);	// wrap
							}
							else
							{
								speed--;	// Next lower speed
							}
						}
					}
					else if (event_switch==1)
					{	
						if (event_type == SW_LO_HI)
						{
							speed++;		// Next higher speed
							if (speed>(MAXSPEEDS-1) )
							{
								speed = 0;	// wrap
							}
						}
					}
				}
			}	// if (event_switch<=3)

		}	// if (bStatus...

// Animate outputs according to current mode
		if (t0cntflg)
		{
			if (mode < (MAXMODES-1) )
			{ // pattern mode
				newPortB = ppattern[mstep];
			}
			else
			{	// random mode
				for (ir=0; ir<4; ir++)
				{	// Repeated 4 times to avoid visible shifting in outputs
					data = LFSR16(0x0);	// get updated random word
				}
				newPortB = (unsigned char) data & 0x0f;	// mask off 4 LSB
			}
			if (mstep>=(npattern-1))
			{
				mstep = 0;
			}
			else
			{
				mstep++;
			}
			t0cntflg = FALSE;
		}

// Override when switch is high (first 4 switches in mode 0)
		if (mode==0)
		{
			if (sw_states[0])
			{
				newPortB |= 0x01;
			}
			if (sw_states[1])
			{
				newPortB |= 0x02;
			}
			if (sw_states[2])
			{
				newPortB |= 0x04;
			}
			if (sw_states[3])
			{
				newPortB |= 0x08;
			}
		}

		PORTB = newPortB;	// Update port

	}	// while(1)

}	// main()

REFERENCES

1. Installation and Setup instructions for the MPLAB IDE and HI-TECH PICC LITE Compiler; which includes useful downloads:
MPLAB IDE + HI-TECH PICC LITE Compiler- Installation and Setup