|
|
View previous topic :: View next topic |
Author |
Message |
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Tue Dec 07, 2004 9:36 pm |
|
|
I'll give this some more thought in the morning and test it on some hardware.
Mark |
|
|
ernest
Joined: 11 Feb 2004 Posts: 51
|
|
Posted: Tue Dec 07, 2004 10:12 pm |
|
|
Mark wrote: | I'll give this some more thought in the morning and test it on some hardware.
Mark |
Thanks a lot Mark for your invaluable assistance & for your time. Really appreciate it very much. |
|
|
Guest
|
|
Posted: Wed Dec 08, 2004 12:03 am |
|
|
Hi lets say for argument sake we rename some of your pins so I can understand them.
#define LATCH PIN_CI // RCLK ---> Pin 12
#define CLK PIN_C2 // SRCLK ---> Pin 11
#define DAT PIN_C0 // SER --> Pin 14
#define OE // OE ---> Pin 13
#define ROWSELECT PORTD
This what I do to drive a led sign we build.
// 0 1 2 3 4 5 6 7 8
int const RowData[9] = {0, 1, 2, 4, 8, 16, 32, 64, 128};
main()
(
// ADD ALL PIC TRIS SETUP
int i,j;
LATCH = 0;
CLK = 0;
DAT = 0;
OE = 0;
while (1)
{
for (i = 1; i < 9; i++) // 8 rows
{
ROWSELECT = RowData[0]; // All rows Off
for (j = 0; j < 80; j++)
{
DAT = 1; // TURN ON LED
CLK = 1;
delay_cycles(2);
CLK = 0; // shift all 80 bits all leds on
}
LATCH = 1;
delay_cycles(2); // Latch Data
LATCH = 0;
ROWSELECT = RowData[i]; // Turn on 1 of the 8 Rows
delay_ms(1); // delay 1ms
}
}
}
Ernest the above code should turn on the whole display.
This is just a test but not the way I would drive data to the HC595s. This is very slow because of bit banging data and clock.
The above example loads data for whole row, then latches data, turn Row to show it, delay 1ms then turns off all rows. This starts over again for new data for the next row.
Continually scans over and over.
I would do the display refresh by interupt. At defined rate.
Since the above example turns ON a row for 1ms x 8 rows = 8ms. or 1/8ms 125 refreshes per second.
This would be true if it took 0sec time to load data into hs595s. So the above rate will be less than 125Hz.
Let me know if this works
Good Luck
Jerry
ernest wrote: | So sorry for the confusion. I'd like to CONFIRM that I have connected the following way:
#define EXP_OUT_ENABLE PIN_C1 // RCLK ---> Pin 12
#define EXP_OUT_CLOCK PIN_C2 // SRCLK ---> Pin 11
#define EXP_OUT_DO PIN_C0 // SER --> Pin 14
Hence, RC1 connected to RCLK & RC2 connected to SRCLK.
From the scope, the RCLK goes high & then low first and next followed by SRCLK going high & low.
The delay of 2us that you've asked to try out works the same way (in terms of LED display still same ie.alternate ON & blank) as the previous test program without the delay. From scope, I can notice that there is a longer gap/interval between each RCLK pulse and SRCLK pulse due to the delay.
ernest
P/S:
#define EXP_OUT_ENABLE PIN_C1 // RCLK ---> Pin 12
#define EXP_OUT_CLOCK PIN_C2 // SRCLK ---> Pin 11
#define EXP_OUT_DO PIN_C0 // SER --> Pin 14
Mark wrote: | Quote: |
#define EXP_OUT_ENABLE PIN_C1 // SRCLK
#define EXP_OUT_CLOCK PIN_C2 // RCLK
#define EXP_OUT_DO PIN_C0 // SER
Previously, I have defined it to the following but it can't work:
#define EXP_OUT_ENABLE PIN_C2 // SRCLK
#define EXP_OUT_CLOCK PIN_C1 // RCLK
|
Something seems a bit strange to me. In the top example you say SRCLK is on C1 while the latter you say it is on C2. Which pin number of the 595 do you have connected to C1 and which one to C2?
What did the delay do for the test program? |
|
|
|
|
Jerry I
Joined: 14 Sep 2003 Posts: 96 Location: Toronto, Ontario, Canada
|
|
Posted: Wed Dec 08, 2004 7:19 am |
|
|
Hi Ernest;
Also try increasing the row on time to 2ms to see if it looks any better, LEDs brighter.
the refresh rate will be lower to 1/2
With a slight change in code below, it will turn on 1 row ON then next row off.
[code]
LATCH = 0;
CLK = 0;
DAT = 0;
OE = 0;
while (1)
{
for (i = 1; i < 9; i++) // 8 rows
{
ROWSELECT = RowData[0]; // All rows Off
for (j = 0; j < 80; j++)
{
DAT = !DAT; // TURN ON/OFF ROW LED DATA
CLK = 1;
delay_cycles(2);
CLK = 0; // shift all 80 bits all leds on
}
LATCH = 1;
delay_cycles(2); // Latch Data
LATCH = 0;
ROWSELECT = RowData[i]; // Turn on 1 of the 8 Rows
delay_ms(1); // delay 1ms try 2ms
}
}
[code]
Good Luck
Jerry
[quote="Anonymous"]
Hi lets say for argument sake we rename some of your pins so I can understand them.
[code]
#define LATCH PIN_CI // RCLK ---> Pin 12
#define CLK PIN_C2 // SRCLK ---> Pin 11
#define DAT PIN_C0 // SER --> Pin 14
#define OE // OE ---> Pin 13
#define ROWSELECT PORTD
This what I do to drive a led sign we build.
// 0 1 2 3 4 5 6 7 8
int const RowData[9] = {0, 1, 2, 4, 8, 16, 32, 64, 128};
main()
(
// ADD ALL PIC TRIS SETUP
int i,j;
LATCH = 0;
CLK = 0;
DAT = 0;
OE = 0;
while (1)
{
for (i = 1; i < 9; i++) // 8 rows
{
ROWSELECT = RowData[0]; // All rows Off
for (j = 0; j < 80; j++)
{
DAT = 1; // TURN ON LED
CLK = 1;
delay_cycles(2);
CLK = 0; // shift all 80 bits all leds on
}
LATCH = 1;
delay_cycles(2); // Latch Data
LATCH = 0;
ROWSELECT = RowData[i]; // Turn on 1 of the 8 Rows
delay_ms(1); // delay 1ms
}
}
}
[code]
Ernest the above code should turn on the whole display.
This is just a test but not the way I would drive data to the HC595s. This is very slow because of bit banging data and clock.
The above example loads data for whole row, then latches data, turn Row to show it, delay 1ms then turns off all rows. This starts over again for new data for the next row.
Continually scans over and over.
I would do the display refresh by interupt. At defined rate.
Since the above example turns ON a row for 1ms x 8 rows = 8ms. or 1/8ms 125 refreshes per second.
This would be true if it took 0sec time to load data into hs595s. So the above rate will be less than 125Hz.
Let me know if this works
Good Luck
Jerry[/code] |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Dec 08, 2004 8:33 am |
|
|
The code worked fine in my test hardware. From your measurements though, it seems as though there is some cross talk on the lines. Lets invert the normal state of the lines and see if it works better. I made changes to the #defines to better match the 595.
Code: |
#define PIN_RCLK PIN_C1 // RCLK ----> Pin 12
#define PIN_SCLK PIN_C2 // SRCLK ---> Pin 11
#define PIN_SER_IN PIN_C0 // SER -----> Pin 14
output_high(PIN_RCLK);
output_high(PIN_SCLK);
// The first bit is a 1, so put it there
output_high(PIN_SER_IN);
output_low(PIN_SCLK);
delay_us(1);
output_high(PIN_SCLK);
output_low(PIN_SER_IN);
for (i=0;i<80;i++) // we've 80columns to
{
// put the shift register values on the outputs
output_low(PIN_RCLK);
delay_us(1);
output_high(PIN_RCLK);
delay_us(1);
// rotate the 1 through the 595's
output_low(PIN_SCLK);
delay_us(1);
output_high(PIN_SCLK);
delay_us(50); // adjust this value to control the drive time
} // END for-loop
|
See what result you get with this. |
|
|
ernest
Joined: 11 Feb 2004 Posts: 51
|
|
Posted: Wed Dec 08, 2004 6:42 pm |
|
|
I've tried the test program with my prototype & all the LEDs are successfully lighted up when I output 0xFF to them.
But when I use the codes to display char. some problems appear. For certain/some columns, the problem still appears. In fact, sometimes the brightness will change/intensify with very short flashes.
The char.'D' & 'T' are not able to be displayed properly with the 2nd column of'D' & 3rd column of 'T' being blanked and skipped.
ernest
Program that I use to display char.
Code: |
delaycount=4;
while (delaycount)
{
index = startposition;
output_high(PIN_RCLK);
output_high(PIN_SCLK);
// The first bit is a 1, so put it there
output_high(PIN_SER_IN);
output_low(PIN_SCLK);
delay_us(1);
output_high(PIN_SCLK);
output_low(PIN_SER_IN);
for (i=0;i<80;i++) // we've 80columns to
{
// point to the next pattern to display
(long)index = (long)i + (long)startposition;
// ensure that we don't exceed the array
if (index >= ((long)(6*s1_char_size)))
index -= ((long)(6*s1_char_size));
port_d=0;
// put the shift register values on the outputs
output_low(PIN_RCLK);
delay_us(1);
output_high(PIN_RCLK);
delay_us(1);
// rotate the 1 through the 595's
output_low(PIN_SCLK);
delay_us(1);
output_high(PIN_SCLK);
x = s1[index/6];
if((index%6)==0) // if index MODULUS 6 == 0
port_d = 0;
else if( x < 64) // check if s1[] is inside ascii1[] array
port_d = ascii1[x-32][(index%6)-1];
else if( x > 95) // check if s1[] is inside ascii3[] array
port_d = ascii3[x-96][(index%6)-1];
else if( x>63 && x<96 ) // check if s1[] is inside ascii2[] array
port_d = ascii2[x-64][(index%6)-1];
delay_us(50); // adjust this value to control the drive time
} // END for-loop
|
|
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Dec 08, 2004 7:23 pm |
|
|
What else does the program do? Any ints? Could you post the whole thing? Also, did you get rid of the leading blank for each char making your const data only 5 members? |
|
|
ernest
Joined: 11 Feb 2004 Posts: 51
|
|
Posted: Wed Dec 08, 2004 7:48 pm |
|
|
Mark wrote: | What else does the program do? Any ints? Could you post the whole thing? |
Can I send my whole program to your email? I believe it is quite lengthy.
Besides displaying the chars., the program needs to be able to allow user to enter new or edit the message for display.
ernest |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Dec 08, 2004 7:53 pm |
|
|
Edit
Last edited by Mark on Wed Dec 08, 2004 10:01 pm; edited 1 time in total |
|
|
ernest
Joined: 11 Feb 2004 Posts: 51
|
|
Posted: Wed Dec 08, 2004 8:05 pm |
|
|
Mark wrote: | What else does the program do? Any ints? Could you post the whole thing? Also, did you get rid of the leading blank for each char making your const data only 5 members? |
I have get rid of all the leading zero/blank for each char. So each const data has a size of 5 elements. So the leading blank is inserted by the following code:
Code: |
if((index%6)==0) // if index MODULUS 6 == 0
port_d = 0;
|
I've also emailed the whole program to u.
Thank You.
ernest
P/S: Is the something wrong with my hardware? |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Wed Dec 08, 2004 10:03 pm |
|
|
Okay, try this out and tell me what it does. I'm not sure about your hardware. But I am pretty sure this should work or be close to working. Didn't have time to think about the scrolling too much. I'll just let you test it
Code: |
#if defined(__PCM__)
#include <16F877A.H>
#fuses HS,NOWDT,NOPUT,NOPROTECT,NOBROWNOUT,NOLVP
#use delay(clock=20000000)
#use rs232(baud=9600, xmit=PIN_A4, rcv=PIN_A5,RESTART_WDT, ERRORS)
#define PIN_RCLK PIN_C1 // RCLK ----> Pin 12
#define PIN_SCLK PIN_C2 // SRCLK ---> Pin 11
#define PIN_SER_IN PIN_C0 // SER -----> Pin 14
#define NUMBER_OF_74595 10
#ENDIF
//#include <74595_V02.C>
#byte port_a=5
#byte port_b=6
#byte port_c=7
#byte port_d=8
#use fast_io(C)
#use fast_io(D)
long s1_char_size=41;
char s1[79];
long i;
void get_line1(void)
{
for(i=0; i<s1_char_size; i++)
{
if(kbhit())
{
s1[i]=getc();
delay_cycles(2);
printf("%C",s1[i]);
delay_us(1);
}
else
i -= 1;
}
} // END of void get_line1()
char const msg_default[] = " Welcome to SDTC <C>Ernest-->2004 (er)";
char const ascii1[32][5] = {
{0,0,0,0,0} // 0
{0,0,95,0,0} // {0,95,0,0,0,0}
// 1
{0,7,0,7,0} // {0, 7,0,7,0,0}
// 2
{20,127,20,127,20} // 3
{36, 42, 127, 42, 18} // 4
{39, 21, 107, 84, 114} // 5
{54, 73, 86, 32, 80} // 6
{0, 11, 7,0,0} // {0, 11, 7,0,0,0}
// 7
{0, 28, 34, 65,0} // {0, 28, 34, 65,0,0}
// 8
{0, 65, 34, 28,0} // {0, 65, 34, 28,0,0}
// 9
{42, 28, 127, 28, 42} // 10
{8, 8, 62, 8, 8} // 11
{0, 88, 56, 0,0} // {0, 88, 56,0,0,0}
// 12
{8,8,8,8,8} // 13
{0, 96, 96, 0,0} // {0, 96, 96,0,0,0}
// 14
{32, 16, 8, 4, 2} // 15
{62, 81, 73, 69, 62} // 16
{0, 66, 127, 64,0} // {0,66, 127, 64,0,0}
// 17
{114, 73, 73, 73, 70} // 18
{34, 65, 73, 73, 54} // 19
{24, 20, 18, 127, 16} // 20
{39, 69, 69, 69, 57} // 21
{60, 74, 73, 73, 48} // 22
{1, 113, 9, 5, 3} // 23
{54, 73, 73, 73, 54} // 24
{6, 73, 73, 41, 30} // 25
{0, 54,54,0,0} // {0, 54, 54,0,0,0}
// 26
{0, 91, 59, 0,0} // {0, 91, 59,0,0,0} // 27
{8, 20, 34, 65,0} // 28
{20, 20, 20, 20, 20} // 29
{65, 34, 20, 8,0} // 30
{2, 1, 81, 9, 6} // 31
};
char const ascii2[32][5] = {
{50, 73, 121, 65, 62} // 0
{124, 18, 17, 18, 124} // 1 A
{65, 127, 73, 73, 54} // 2 B
{62, 65, 65, 65, 34} // 3 C
{65, 127, 65, 65, 62} // 4 D
{127, 73, 73, 65, 65 } // 5 E
{127, 9, 9, 1, 1} // 6
{62, 65, 73, 73, 58} // 7
{127, 8, 8, 8, 127} // 8
{0, 65, 127, 65, 0} // {0, 65, 127, 65,0,0}
// 9
{32, 64, 65, 63, 1} // 10
{127, 8, 20, 34, 65} // 11
{127, 64, 64, 64, 64} // 12
{127, 2, 12, 2, 127} // 13
{127, 2, 4, 8, 127} // 14
{62, 65, 65, 65, 62} // 15
{127, 9, 9, 9, 6} // 16
{62, 65, 81, 33, 94} // 17
{127, 9, 25, 41, 70} // 18
{38, 73, 73, 73, 50} // 19
{1,1,127, 1, 1} // 20
{63, 64, 64, 64, 63} // 21
{7, 24, 96, 24, 7} // 22
{127, 32, 24, 32, 127} // 23
{99, 20, 8, 20, 99} // 24
{3, 4, 120, 4, 3} // 25
{97, 81, 73, 69, 67} // 26
{0, 127, 65, 65,0} // {0, 127, 65, 65,0,0}
// 27
{2, 4, 8, 16, 32} // 28
{0, 65, 65, 127,0} // {0, 65, 65, 127,0,0}
// 29
{4, 2, 1, 2, 4} // 30
{64, 64, 64, 64, 64} // 31
};
char const ascii3[31][5] = { // 0 -- 30 == 31 elements
{0, 0, 7, 11,0} // {0, 7, 11,0,0,0}
// 0
{32, 84, 84, 84, 56} // 1
{127, 40, 68, 68, 56} // 2
{56, 68, 68, 68,0} // 3
{56, 68, 68, 40, 127} // 4
{56, 84, 84, 84, 24} // 5
{8, 126, 9, 9, 2} // 6
{8, 84, 84, 84, 60} // 7
{127, 8, 4, 4, 120} // 8
{0, 68, 125, 64,0} // {0, 68, 125, 64,0,0}
// 9
{32, 64, 68, 61,0} // 10
{127, 16, 40, 68,0} // 11
{0, 65, 127, 64, 0} // {0, 65, 127, 64,0,0}
// 12
{124, 4, 120, 4, 120} // 13
{124, 8, 4, 4, 120} // 14
{56, 68, 68, 68, 56} // 15
{124, 20, 20, 20, 8} // 16
{8, 20, 20, 20, 124} // 17
{124, 8, 4, 4, 8} // 18
{72, 84, 84, 84, 36} // 19
{4, 63, 68, 68,0} // 20
{60, 64, 64, 32, 124} // 21
{28, 32, 64, 32, 28} // 22
{60, 64, 48, 64, 60} // 23
{68, 40, 16, 40, 68} // 24
{12,80, 80, 80, 60} // 25
{68, 100, 84, 76, 68} // 26
{0, 8, 54, 65,0} // {0, 8, 54, 65,0,0}
// 27
{0,0,119,0,0} // {0, 119,0,0,0,0} // 28
{0, 65, 54, 8,0} // {0, 65, 54, 8,0,0}
// 29
{2,1,2,4,2} // 30
};
void main(void)
{
int char_index;
int col_index;
int x = 0;
long MSD, LSD;
int delaycount;
int16 startposition;
set_tris_b(0); //set portb to outputs
set_tris_c(0x90); //set portc to outputs, C7 to input
set_tris_d(0);
port_b = 0;
port_c = 0; //zero port c
startposition = 0;
for(i=0; i<79; i++)
s1[i] = 46;
for(i=0; i<sizeof(msg_default); i++)
s1[i] = msg_default[i];
delay_ms(100);
do
{
if(!input(PIN_E1)) //if RE1==LOW (pressed)
{
do
{
delay_cycles(2);
printf("\f\r\n");
printf("\f\r\n How many char.(0--99):");
MSD=getc();
delay_cycles(2);
printf("%C",MSD);
delay_us(1);
}while(!isamoung(MSD,"0123456789"));
do
{
LSD=getc();
delay_cycles(2);
printf("%C",LSD);
delay_us(1);
}while(!isamoung(LSD,"0123456789"));
s1_char_size = 10*(MSD-48) + (LSD-48);
delay_cycles(2);
printf("\r\n\n Enter the %lu char.: ",s1_char_size);
get_line1(); // acquire 16 char.
} // END of INPUT(PIN_E1)
// The delay count controls how fast the message scrolls
delaycount=4;
while (delaycount)
{
// This will be the index into our character
col_index = startposition % 6;
// This will be the index into our display msg array
char_index = startposition / 6;
// Make sure that our index doesn't exceed our array size
if (char_index >= sizeof(s1))
{
startposition = 0;
char_index = 0;
col_index = 0;
}
// grab the first char
x = s1[char_index];
output_high(PIN_RCLK);
output_high(PIN_SCLK);
// The first bit is a 1, so put it there
output_high(PIN_SER_IN);
output_low(PIN_SCLK);
delay_us(1);
output_high(PIN_SCLK);
output_low(PIN_SER_IN);
for (i=0;i<80;i++) // we've 80 columns to
{
port_d=0;
// put the shift register values on the outputs
output_low(PIN_RCLK);
delay_us(1);
output_high(PIN_RCLK);
delay_us(1);
// rotate the 1 through the 595's
output_low(PIN_SCLK);
delay_us(1);
output_high(PIN_SCLK);
// if the column index is 5, then grab the next char from our array
if (col_index == 5)
{
// This is the 6th column so grab the next char
col_index = 0;
char_index++;
if (char_index >= sizeof(s1))
char_index = 0;
x = s1[char_index];
}
else
{
// This is an invalid char. Note that we do not really need to set portd since it is already 0
if ( x < 32)
port_d = 0;
// check if s1[] is inside ascii1[] array
else if( x < 64)
port_d = ascii1[x-32][col_index];
// check if s1[] is inside ascii2[] array
else if( x < 96 )
port_d = ascii2[x-64][col_index];
// check if s1[] is inside ascii3[] array
else if( x < 128)
port_d = ascii3[x-96][col_index];
// Any other invalid chars won't get printed either
// Move to the next column in the char
col_index++;
}
//adjust this value to control the drive time
delay_us(120);
}// END for-loop
// decrement our delay loop counter
delaycount--;
}
//Point to next data pattern
startposition++;
} while(1); //End of do-while
}// END main()
|
|
|
|
ernest
Joined: 11 Feb 2004 Posts: 51
|
|
Posted: Thu Dec 09, 2004 12:55 am |
|
|
I have tested the program out but the problem still exist especially at the second half of the columns (column 40-79). The 2nd column for the char.'D' has been skipped & shfited.
I'll email the photo of the LED display & a moving pic.of it to u.
Thank You.
ernest |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Thu Dec 09, 2004 7:29 am |
|
|
Okay, from the movie, it looks like the 595's are getting some noise or cross talk. Lets make it a little easier to see by commenting out
This will prevent the message from scrolling and we can see what kind of jumping around we get. |
|
|
Mark
Joined: 07 Sep 2003 Posts: 2838 Location: Atlanta, GA
|
|
Posted: Thu Dec 09, 2004 7:46 am |
|
|
Declare this variabe
and change the for loop to
Code: |
for (i=0;i<80;i++) // we've 80 columns to
{
port_d=0;
port_d_mask = 0;
// if the column index is 5, then grab the next char from our array
if (col_index == 5)
{
// This is the 6th column so grab the next char
col_index = 0;
char_index++;
if (char_index >= sizeof(s1))
char_index = 0;
x = s1[char_index];
}
else
{
// This is an invalid char. Note that we do not really need to set portd since it is already 0
if ( x < 32)
port_d_mask = 0;
// check if s1[] is inside ascii1[] array
else if( x < 64)
port_d_mask = ascii1[x-32][col_index];
// check if s1[] is inside ascii2[] array
else if( x < 96 )
port_d_mask = ascii2[x-64][col_index];
// check if s1[] is inside ascii3[] array
else if( x < 128)
port_d_mask = ascii3[x-96][col_index];
// Any other invalid chars won't get printed either
// Move to the next column in the char
col_index++;
}
// put the shift register values on the outputs
output_low(PIN_RCLK);
delay_us(1);
output_high(PIN_RCLK);
delay_us(1);
// rotate the 1 through the 595's
output_low(PIN_SCLK);
delay_us(1);
output_high(PIN_SCLK);
// Set the actuall data to port_d
port_d = port_d_mask;
//adjust this value to control the drive time
delay_us(120);
}// END for-loop
|
This will give us a little more time to turn off the previous column before we enable the next. |
|
|
ernest
Joined: 11 Feb 2004 Posts: 51
|
|
Posted: Thu Dec 09, 2004 7:09 pm |
|
|
I've tried out the lastest codes that you sent to me.
The results are almost similar to the previous. I've
also tried to set "port_d_mask=0xFF" with the
"startposition++" commented out to let all the LED to
light up except every column5 interval being blanked.
I'm trying to add in more ground plane and connection
to make sure the ground is properly done for the
board. Do you need to have a look at my schematic?
I've sent a pic.. & movie of the 80 columns LED.
Thanks.
ernest |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|