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