The DIY PIC Development Board was used with a PIC 16F876A. A typical "wall-wart" power-supply is used (a surplus laptop charger in this case) in conjunction with a voltage regulator (LM7805) to provide the regulated 5V required by the PIC microcontroller and the rest of the circuitry.
1 x SN74HC595 + 8 x LED's + PIC16F876A
The connection of the SN74HC595 (see Schematic Diagram section) is relatively straight forward with there being three control lines from the PIC16F876A and eight output lines from the shift register (in this case controlling a series of LED's):
- Pin 12: RCLK - this is the data latch pin, when the voltage changes from low to high, the data in the shift register part of the SN74HC595 is transfered into the storage register part of the SN74HC595 (i.e., the output pins of the SN74HC595 now reflect the state of the input serial data, and don't change until pin 12 changes from low to high again)
- Pin 11: SRCLK - this is the "clock" pin, when the voltage changes from low to high, the input at the serial data input pin (SER pin 14) is "clocked" into the first stage of the shift register (with data shifting to each successive stage from the previous stage)
- Pin 10: this is the reset pin. This can be left connected to Vcc (i.e. 5V). If this is connected to a switch (or PIC microprocessor) to ground, then when pin 10 goes low, the shift register is cleared
- Pin 9: Qh` - this is the "data out" pin. Connect pin 9 to pin 14 of the next successive SN74HC595 in order to daisy-chain shift registers
- Pin 15 & 1 to 7: these are the output pins Qa to Qh respectively.
- Pin 14: SER - this is the serial data input pin
- Pin 13: OE - this is the enable pin. Connect pin 13 to ground to enable the shift register. If this is connected to a switch (or PIC microprocessor) if connected to Vcc (i.e. 5V) then the output pins are disabled, otherwise, when connected to ground, the output pins are enabled.
Connection to the PIC microprocessor utilises any convenient output pins (in this case, pins RC1, RC3 and RC5 from Port C). The connection of the PIC microprocessor to enable basic operation (i.e. power supply, crystal oscillator, ICSP etc) is detailed in the DIY PIC Development Board. Connection of the LED's, which are used as output devices to demonstrate the state of the shift register outputs, is straight forward (note the current limiting resistors).
2 x SN74HC595 daisy-chained + 16 x LED's + PIC16F876A
The connection of the shift registers is analoguous to that of a single shift register. The clock (pin 11) and data latch (pin 12) are connected from both shift registers to the appropriate pin on the PIC microcontroller (i.e. as a "bus"). The serial data output from PIC microcontroller (pin RC5 in this instance) goes to the first shift register's serial data input pin (pin 14). However, then pin 9 of the first shift register is connected to pin 14 of the second shift register (and so on if further shift registers were daisy-chained in sequence).
4 x SN74HC595 + 64 x RGB LED's + PIC16F876A
The various shift registers are connected daisy-chained together analoguous to that detailed for the two shift registers in the previous section. Pin 11 on each of the shift registers are connected together and then to the clock pin of the PIC microcontroller. Similarly, pin 12 on each of the shift registers are connected together and then to the data latch pin designated on the PIC microcontroller. The serial data output from PIC microcontroller (pin RC5 in this instance) goes to the first shift register's serial data input pin (pin 14). The pin 9 of the first shift register is connected to pin 14 of the second shift register and so on for the other two shift registers in sequence.
The major difference in the wiring of the 4 x SN74HC595 to control the 64 x RGB LED's is concerned with how the RGB LED's operate. The 4 x SN74HC595 shift registers form in effect a four byte data structure that represents the state of the RGB LED's, which in turn can be represented as a 8 x 8 matrix. Each RGB LED belongs to row and column in the matrix, while at the same time, each RGB LED physically has three anodes (one each for the red, green and blue "channel"). The first shift register is choosen to represent the blue channel, the second shift register the green channel, the third shift register the red channel, and finally the fourth shift register represents the "row" of the RGB LED matrix. The output pins of the shift registers representing the red, green and blue "channels" are connected to the respective anode pins of the RGB LED's. Thus all the blue LED anodes in column 1 of the RGB LED matrix are connected to the first output pin of the first shift register, and all the blue LED anodes in column 2 to the second output pin of the first shift register and so forth. Similarily, all the green LED anodes column 1 of the RGB LED matrix are connected to the first output pin of the second shift register, and all the blue LED anodes in column 2 to the second output pin of the second shift register and so forth. Again, similar for the red LED anodes but connecting to the third shift register. Finally, all the cathodes of the first row of the RGB LED matrix are connected to the first outpu pin of the fourth shift register, and so on for the remainder of the cathodes.
Therefore, in order to light a particular RGB LED for a particular colour, the correct bit within the fourth shift register needs to be set to select the correct cathode (i.e. connect the row to ground) and then the correct bits within the first three shift registers need to be set to select which anodes are connected to Vcc, and hence light the appropriate LED. This highlights a couple of other features of the circuit:
- Enabling "rows" to be selected means that individual RGB LED's can be indexed selectivity, and this then allows PWM (using bit angle modulation BAM) to produce desired shades of colours
- There is the possibility that all RGB LED's within a particular row for a particular colour (ie Red, Green or Blue LED) can be on simultaneously. This would mean ~8mA from each of the output shift register pins controlling the anodes in question, but 8x8mA returning to the shift register pin controlling the common cathodes, which exceeds the 35mA limit for an individual SN74HC595 pin. This is why the fourth shift register (assigned to controlling the common cathodes) in turn controls the output pins of the ULN2003 Darlington transistor array. The ULN2003 can handle 500mA on each pin.
Software/Firmware
The required firmware to output serial data from the PIC microcontroller to the SN74HC595 shift register is relatively straight forward. The PIC microcontroller needs to produce a "clock" signal which at each raising edge (transition from low to high) causes whatever state on the serial data pin to be "clocked" into the shift register. After the appropriate number of bits have been clocked in, then the latch pin is brought high, which causes the shift register to latch the serial input data to the shift register output pins.
The following code snippet shows the process (code snippet 1):
#define HC595_LATCH PIN_C1
#define HC595_CLK PIN_C3
#define HC595_DATA PIN_C5
#define NUM_CASCADED_HC595 2
int8 dataBuffer[NUM_CASCADED_HC595];
void HC595_putData(BYTE* dataByte) {
//note that due to pointers being used, the contents of dataByte are destroyed
//during the putData call, therefore, a seperate buffer/memory location is required
//to store serial data to be output to the shift register
int i;
for(i=1;i<=NUM_CASCADED_HC595*8;++i) { // Clock out bits from the dataByte array
if((*(dataByte+(NUM_CASCADED_HC595-1))&0x80)==0) //determine if the serial
output_low(HC595_DATA); //output pin needs to be
else //set high or low
output_high(HC595_DATA);
shift_left(dataByte,NUM_CASCADED_HC595,0);
output_high(HC595_CLK); //serial data BIT on output pin clocked into shift register
output_low(HC595_CLK);
}
output_high(HC595_LATCH); //input serial data BYTE is latched to the output pins
output_low(HC595_LATCH);
}
void main() {
dataBuffer[0]=0b10101010;
dataBuffer[1]=0b01010101;
HC595_putData(dataBuffer); //LED's lit according to dataBuffer pattern
do { //endless loop
} while (TRUE);
}
Bit Angle Modulation (BAM) - Pulse Width Modulation
RGB LED's are composed of three seperate LED's (a red, a green and a blue, doh!) fabricated into a single package. Hence the four pins, one for each LED "colour" and a common cathode (or anode depending upon the variety in question). When the individual LED's within a RGB LED are turned on in various combinations, the resultant mixing of colours produces the various shades/hues observed. If the individual LED's within a RGB LED can only be turned on or off, then only the seven basic colours can be achieved. If the individual LED's can be controlled to have variable intensity, then potentially any colour can be output on the RGB. Pulse width modulation (PWM) is typically used to vary the intensity. While the PIC microcontroller has the ability to produce PWM output, this is generally limited to only a few output pins (see PIC PWM for examples). In order to produce PWM via the shift registers (i.e. in effect having 192 PWM outputs) bit angle modulation (BAM) is used.
There are a number of sites giving in depth explanation of BAM (1). In short, BAM is a method for turning an output on and off for a certain proportion of a fixed time period (i.e. PWM) but using software/firmware for controlling the on/off periods, and hence enabling PWM on an arbitrary number of outputs. However, note this will be dependant upon the clock frequency/capabilities of the microcontroller, and really is only suited to applications such as control of a large number of RGB LED's, which rely upon persisence of vision (POV) of the human eye and inherently doesn't require particularly fast cycle times etc.
The BAM is implemented using interrupts generated by the PIC16F876 onboard Timer1 module. The frequency of the Timer1 interrupt varies depending upon the desired pulse width calculated by the BAM function. The interrupt routine performs/calls the BAM calculation functions before initiating the timer module again. This "loading up" of the interrupt routine is generally frowned upon as not good practice. However, in this particular application/use of Timer1 interrupts, the actual precise reproducibility of elapsed time between generated interrupts is not of primary importance.
Having the interrupt routine perform the BAM calculations enables the main programme loop to focus solely on performing required calculations/logic processing for animation of the RGB LED's etc, and the BAM calculation/control in effect becomes a "background process". Also, full 8-bits of BAM resolution is not necessary/required in practice to produce a large and pleasing array of potential colours using the RGB LED's (as viewed using the human eye). Therefore, by only using the higher nibble (4 most significant bits) of the "BAMbit" variable, only the longer time periods from Timer1 module are required. In turn this means the processing performed within the interrupt routine in effect becomes a relatively constant offset or addition, to the times calculated/produced by the Timer1 interrupt. Since the actual pulse widths required from each individual LED within the RGB LED to produce a particular desired output colour need to be tailored to the particular LED's in use in any case (as the individual LED's within the RGB LED have different inherent intensities), this time "constant" addition can be easily taken into account.
The following code snippet gives the BAM calculation function and an example of use within the main programme loop (code snippet 2):
#include "hc595.h"
#define RED 2
#define GREEN 0
#define BLUE 1
#define ROW 3
const unsigned int16 BAMtimes[8] = {65530,65525,65515,65495,65455,65375,65215,64895}; //0.002ms
int8 BAMbit;
int8 BAMbitCnt;
int8 dataBuffer[NUM_CASCADED_HC595];
int8 interruptFlag = 0;
int8 RGB_R[8] = {255,255,128,000,000,000,000,255}; //approx "rainbow" of colours
int8 RGB_G[8] = {000,128,255,255,255,128,000,255};
int8 RGB_B[8] = {000,000,000,000,128,255,255,128};
int8 LEDrow = 0b00000100; //MSB -> LSB = Qh -> Qa on HC595
void calcBAM() {
int8 LED;
for (LED=0;LED<8;LED++) {
if ((RGB_R[LED] & BAMbit) == BAMbit) {
shift_right(&dataBuffer[RED],1,1);
} else {
shift_right(&dataBuffer[RED],1,0);
}
if ((RGB_G[LED] & BAMbit) == BAMbit) {
shift_right(&dataBuffer[GREEN],1,1);
} else {
shift_right(&dataBuffer[GREEN],1,0);
}
if ((RGB_B[LED] & BAMbit) == BAMbit) {
shift_right(&dataBuffer[BLUE],1,1);
} else {
shift_right(&dataBuffer[BLUE],1,0);
}
}
LEDrow=LEDrow>>1;
if (LEDrow==0) {LEDrow=0b00000100;}
dataBuffer[ROW] = LEDrow;
}
#int_timer1
void timer1_interrupt() {
//large amount of processing within interrupt routine generally
//"bad practice" - see text for discussion
BAMbitCnt++;
BAMbit=BAMbit<<1; //only using "4 bit" BAM resolution
if (BAMbit==0) {BAMbit=0b00010000;BAMbitCnt=4;}
calcBAM();
HC595_putData(dataBuffer);
set_timer1(BAMtimes[BAMbitCnt]);
interruptFlag=1;
}
void main() {
int i;
int temp_R;
int temp_G;
int temp_B;
setup_adc_ports(NO_ANALOGS);
setup_adc(ADC_OFF);
setup_spi(FALSE);
setup_timer_0(RTCC_INTERNAL);
setup_timer_2(T2_DISABLED,0,1);
setup_comparator(NC_NC_NC_NC);
setup_vref(FALSE);
LEDrow=0b00000100;
BAMbit=0b00010000; //only using "4-bit" BAM
BAMbitCnt=4;
HC595_init();
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8); // Set Timer1 prescaler to 8 : this is the 'long' delay
set_timer1(BAMtimes[BAMbitCnt]);
enable_interrupts(GLOBAL); //To enable interrupts, the global interrupt bit must be set
enable_interrupts(INT_TIMER1); //and then the specific interrupt bits must be set
do {
//main loop cycles the contents of the "colour" arrays
//giving a scrolling rainbow effect on the RGB LED's
delay_ms(500);
temp_R=RGB_R[0];temp_G=RGB_G[0];temp_B=RGB_B[0];
for (i=1;i<8;i++) {
RGB_R[i-1]=RGB_R[i];RGB_G[i-1]=RGB_G[i];RGB_B[i-1]=RGB_B[i];
}
RGB_R[7]=temp_R;RGB_G[7]=temp_G;RGB_B[7]=temp_B;
} while (TRUE);
}
The Testing/Experimental Results Section provides some more detail.
Only Logged-In Members can add comments