|
|
View previous topic :: View next topic |
Author |
Message |
Guest Guest
|
Rcv only software RS232 (interrupt driven) doesn't work |
Posted: Wed Jun 28, 2006 9:35 am |
|
|
First off, I've searched and read a hundred software RS232 posts but nothing about what I'm trying to do. Most use #use rs232 which I tried as well but couldn't get it to work at all.
I was able to use the software SCI routines from Microchip's website to receive a character but the routines are written in assembly. I had a ton of issues trying to incorporate the ASM with my current C project so I figure my options are 1) do the SCI in C or 2) do the whole project in ASM. Now I hardly know any C (only what I learned from this PIC project basically) and even less assembly (only what I learned from slightly modifying the SCI routines) so I need to do it all in C. But I do know my hardware and TTL->RS232 converter is working.
I found a similar routine in C. I had to modify it to use with CCS (I'm using version 3.209) as well as remove all the transmit stuff.
But it's not working. It seems to be with the interrupts.
I guess my main question is whether or not I need to clear the T0IF flag or if that is done automatically. Though I've tried it both with the clearing commented out and with it in.
THe code is attached. Basically, my receive pin on the 16F684 is RA2/T0CKI/INT. It looks for the falling edge start bit then times and receives the bits. This C routine seems to be the same method the ASM SCI routine on Microchip's site uses.
ANyone offer some help? I've gotten NOWHERE in days.
Code: | #include <16F684.h>
#include "defs.h"
#use delay(clock=8000000)
// register definitions (for direct access)
#BYTE OPTION_REG = 0x81
#BYTE INTCON = 0x0B
#BYTE PORTA = 0x05
#BIT T0IF = 0x0B.2
/* Function prototypes */
void Setup(void);
char GetChar(void);
void StartBitDetect(void);
void RcvNextBit(void);
/* Global Register assignments */
char RxByte; // The Received Byte
char TxRxBitCount; // Bit counter for TX/RX
char SerialStatus; // Serial status register
char data; // General purpose register
/* Bits of SerialStatus */
#define TxOver 0 // Set when byte transmitted
#define TxEnable 1 // Set to enable transmitter
#define RxOver 2 // Set when byte received
#define RxFrameError 3 // Set when stop bit not zero
#define RxStarted 4 // Set when receive character is in progress
#define RxBit 5 // Unused.
/* Bit Mask definitons */
#define RxInputMask 0x04 // Receiver input pin is connected to RA2, ie. bit 2 of Port A
#define TxOverMask 0x01
#define TxEnableMask 0x02
#define RxOverMask 0x04
#define RxFrameErrorMask 0x08
#define RxStartedMask 0x10
#define RxBitMask 0x20
#define IntconRtcc 0x04
/******************************/
/* TxRxBitCount event numbers */
/* ( Transmit mode ) */
/******************************/
#define StartBit 10
#define Bit0 9
#define Bit1 8
#define Bit2 7
#define Bit3 6
#define Bit4 5
#define Bit5 4
#define Bit6 3
#define Bit7 2
#define StopBit 1
/* Port and system assignments */
#define Baud9600Rate 154 // TMR0 Rate for 8 Mhz clock
#define Baud9600RatePlusHalf 130 // Start bit to first data bit delay (8Mhz)
#define PortAConfig 0x0F // Configure port A
#define PortCConfig 0x20 // Configure port C
#define RxMode 0x40 // Set option reg TMR0 interrupt source from counter - Max speed prescaler ( 1:2 )
#define RxStartbitMode 0x78 // TMR0 source is External Clock (falling edge),
// Assign prescaler to Watchdog timer for detection of START BIT
void main(void)
{
setup_oscillator(OSC_8MHZ); // set internal oscillator to 8 MHz
setup_comparator(NC_NC_NC_NC); // turn off comparators (sets comparator ports to digital I/O)
setup_adc(ADC_OFF); // turn A/D off (sets A/D ports to digital I/O)
Setup(); // initalize ports, flags, etc for RS232 receive
while(TRUE)
{
data = GetChar(); // receive the next character
if ( SerialStatus & RxFrameErrorMask ) // check if there was an error
data = 'E'; // store capital E to indicate invalid character
}
}
#INT_TIMER0
void ISR_Timer0( void )
{
if ( ( SerialStatus & RxStartedMask ) == 0 )
StartBitDetect(); // initialize Receiver (wait for a start bit)
else
RcvNextBit(); // else receive the next bit
}
void Setup(void)
{
output_low(PIN_C0);
output_low(PIN_C1);
output_low(PIN_C2);
OPTION_REG = RxMode; // set Option register to receiver mode
set_tris_a( PortAConfig );
set_tris_c( PortCConfig );
TxRxBitCount = StartBit; // Setup bit count
SerialStatus = 0; // Clear other flags
bit_set( SerialStatus, TxOver ); // Set the TxOver status for 'Ready'
//bit_clear( INTCON, T0IF ); // Clear TMR0 overflow flag
disable_interrupts( INT_TIMER0 );
enable_interrupts( GLOBAL );
}
char GetChar(void)
{
SerialStatus = 0; // Clear Status flags
TxRxBitCount = 9; // 8 data bits + 1 stop bit
RxByte = 0; // Clear Rxbyte
OPTION_REG = RxStartbitMode; // Set Option register to receiver mode
set_timer0( 0xFF ); // load Timer0 with 255 (wait for start bit)
//bit_clear( INTCON, T0IF ); // Clear TMR0 overflow flag
enable_interrupts( INT_TIMER0 );
while( ( SerialStatus & RxOverMask ) == 0 ); // Wait for character ready
//bit_set( SerialStatus, TxEnable ); // Enable Transmitter-
//bit_set( SerialStatus, TxOver ); // -ready for next tx byte
TxRxBitCount = StartBit;
disable_interrupts( INT_TIMER0 );
//bit_clear( INTCON, T0IF ); // Clear TMR0 overflow flag
return RxByte; // Return with the received character
}
void StartBitDetect(void)
{
if ( ( PORTA & RxInputMask ) == 0 ) // If RxInput is 0
{
// Start bit detected
bit_set( SerialStatus, RxStarted); // Set Rx started flag
OPTION_REG = RxMode; // set OPTION_REG to wait for first data bit
set_timer0(Baud9600RatePlusHalf); // Set the timer to wait 1.5 x bit time
//bit_clear( INTCON, T0IF ); // Clear TMR0 overflow flag
enable_interrupts( INT_TIMER0 );
}
else // False Start bit detected
{
set_timer0(0xFF);
OPTION_REG = RxStartbitMode; // Set Option register to receiver mode
bit_clear( SerialStatus, RxStarted );
//bit_clear( INTCON, T0IF ); // Clear TMR0 overflow flag
enable_interrupts( INT_TIMER0 );
}
}
void RcvNextBit(void)
{
if ( TxRxBitCount == 1 ) // Is this the stop bit ?
{
if ( ( PORTA & RxInputMask ) == 0 ) // Framing error! Stop bit was not '1'
bit_set( SerialStatus, RxFrameError );
bit_clear( SerialStatus, RxStarted ); // Stop further RTCC interrupts.
bit_set( SerialStatus, RxOver ); // Signal GetChar() that byte available
disable_interrupts( INT_TIMER0 );
//bit_clear( INTCON, T0IF ); // Clear TMR0 overflow flag
// Receive done
}
else
{
rotate_right( RxByte, 1 ); // Shift RxByte right, for next bit
if ( ( PORTA & RxInputMask ) != 0 )
bit_set( RxByte, 7 );
else
bit_clear( RxByte, 7 );
set_timer0( get_timer0() + Baud9600Rate );
//bit_clear( INTCON, T0IF ); // Clear TMR0 overflow flag
enable_interrupts( INT_TIMER0 );
}
TxRxBitCount--; // Count down bits received
} |
|
|
|
rwyoung
Joined: 12 Nov 2003 Posts: 563 Location: Lawrence, KS USA
|
|
Posted: Wed Jun 28, 2006 10:25 am |
|
|
Not sure why you have so much trouble with the #use RS232 pragma. It works very well.
Did you look at the examples "ex_sisr.c" and "ex_stisr.c" closely? It really doesn't get any simpler for interrupt handling and UARTs.
Keep in mind that you must be using the hardware UART for these examples to work properly.
What you describe below sounds like you are trying to to use a part without the UART (16F684). In which case you can still use #use RS232 to configure a software UART and try to use the INT pin as your RX. You need to create an ISR that will trigger on the falling edge (check on that) then inside the ISR it calls "getch" and shoves things into a software FIFO.
I think RJ Hammlet has several posts (or it might be Mark or PCM Programmer) on this subject on the board. _________________ Rob Young
The Screw-Up Fairy may just visit you but he has crashed on my couch for the last month! |
|
|
Guest
|
|
Posted: Wed Jun 28, 2006 11:08 am |
|
|
Yeah the 16F684 doesn't have a hardware UART.
I don't understand what the deal with #int_rda is about. I don't have that. I'm guessing its a hardware thing?
I have a project with the code I posted above in then I started a new one to try out #use rs232 but it doesn't work either. I'm not sure how the kbhit() works. I ran a while() loop with if ( kbhit() ) in it but it would get there whether or not I typed a character. I thought that function should only return true if a start bit is being sent to the RCV bit but it would return true without me sending anything form Hyperterminal. |
|
|
Guest
|
|
Posted: Wed Jun 28, 2006 12:25 pm |
|
|
The #use rs232 doesn't work either. I keep getting referred to archive forum threads but none of them are of any use.
I pretty much copied EX_ENCRY.C... see my 2nd project code below:
Code: | #include <16F684.h>
#include "defs.h"
#use delay(clock=8000000)
#use rs232(baud=1200, rcv=PIN_A2, parity=n, bits=8)
/* Local function prototypes */
void IOInit(void);
U8 bgetc(void);
#define BUFFER_SIZE (16)
U8 buffer[BUFFER_SIZE];
U8 next_in;
U8 next_out;
#INT_EXT
void isr_ext(void)
{
U8 t;
buffer[next_in] = getc();
t = next_in;
next_in = ( next_in + 1 ) % BUFFER_SIZE;
if ( next_in == next_out )
next_in = t; // buffer full
}
#define bkbhit ( next_in != next_out )
U8 bgetc(void)
{
U8 c;
while( !bkbhit ); // wait for start bit
c = buffer[next_out];
next_out = (next_out + 1 ) % BUFFER_SIZE;
return(c);
}
void main(void)
{
U8 c;
IOInit();
while(TRUE)
{
c = bgetc();
}
}
void IOInit(void)
{
setup_oscillator(OSC_8MHZ);
setup_adc(ADC_OFF);
setup_comparator(NC_NC_NC_NC);
set_tris_a(0x0F);
set_tris_c(0x20);
output_low(PIN_C0);
output_low(PIN_C1);
output_low(PIN_C2);
ext_int_edge(H_TO_L);
enable_interrupts(INT_EXT);
enable_interrupts(GLOBAL);
} |
As with the other one, it doesn't seem to interrupt. I put a breakpoint after the 'while(!bkbhit);' but it never gets pasts it when I send a character, neither does it get to the ISR for the INT_EXT. I have a scope on the pin and its sending characters (not to mention the ASM routines I used work fine) but neither of these C software RS232 w/ interrupts work. |
|
|
Guest
|
|
Posted: Wed Jun 28, 2006 1:52 pm |
|
|
SOrry to keep replying to my own thread but I've gotten nowhere still (and there's no edit function).
One thing that may be of concern is that in BOTH of my C versions of software RS232 w/ interrupts, bit 2 of PORTA is never 1. PORTA is 0 straight across after running either of my projects and halting it.
My test project for ASM software RS232 has PORTA has 0's except bit 2 which is 1. So that is obviously correct.
So I guess possibly the reason why neither of my C versions don't interrupt or anything is because there is no High to Low transition on the RA2/INT pin. But putting the scope on the wire shows it has high, and I can see it drop when I type something, but for some reason though the pin is set to input, it's not reading HIGH. I tried using the newly added 'variable = input_state(PIN_A2)' but it constantly says 'Out of Scope'... I dunno |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Wed Jun 28, 2006 2:26 pm |
|
|
There's an edit function if you register and create a user name.
Then just select the option to automatically log you in.
Try the simple program shown below. Put a breakpoint on the
"delay_cycles(1)" line, and put the variable 'c' in a Watch window.
I don't have this PIC, but I tested it with vs. 3.249 with a 16F877
and it did work. When it breaks, you will see that 'c' is the value
that you typed in on the keyboard (displayed in hex format).
You didn't say what version of the compiler you have. I looked
at the .LST file for vs. 3.249 for the program below, and it looks OK.
I did notice there's a bug in the start-up code, that's probably been
there a long time. If you do a soft UART and you only specify the
'rcv' pin, the compiler inserts start-up code to set pin A0 as a low
level output pin -- in other words, it defaults to configuring Pin A0
as the 'xmit' pin even though you didn't specify it. That's why I
added Pin C2 as the xmit pin. If another pin is more convenient,
then change it to that pin. You can't use pins A0 or A1 because
those are used by the debugger.
Code: |
#include <16F684.h>
#fuses INTRC_IO, NOWDT, PUT, BROWNOUT
#use delay(clock=8000000)
#use rs232(baud=1200, xmit=PIN_C2, rcv=PIN_A2)
//===================================
void main(void)
{
char c;
while(1)
{
c = getc();
delay_cycles(1);
}
} |
If this doesn't work, then post your compiler version.
----------------------------
Also, it's a lot easier and more fun to test a serial port program
if you can echo the received characters back to your PC.
(Instead of setting a breakpoint and using a Watch window).
So consider using the following program instead:
Code: | #include <16F684.h>
#fuses INTRC_IO, NOWDT, PUT, BROWNOUT
#use delay(clock=8000000)
#use rs232(baud=1200, xmit=PIN_C2, rcv=PIN_A2)
//===================================
void main(void)
{
char c;
while(1)
{
c = getc(); // Wait for a char from the PC
putc(c); // Then send it back to the PC
}
} |
|
|
|
Douglas Kennedy
Joined: 07 Sep 2003 Posts: 755 Location: Florida
|
|
Posted: Thu Jun 29, 2006 6:53 am |
|
|
There are many posts about issues with RS232. It is important to heed the advice of PCM programmer.
First write the smallest simplest program that is possible to prove out the the interface
Here is PCM programmmers example
Code: | #include <16F684.h>
#fuses INTRC_IO, NOWDT, PUT, BROWNOUT
#use delay(clock=8000000)
#use rs232(baud=1200, xmit=PIN_C2, rcv=PIN_A2)
//===================================
void main(void)
{
char c;
while(1)
{
c = getc(); // Wait for a char from the PC
putc(c); // Then send it back to the PC
}
} |
Once that works then you need to stop and think before adding additional code.
Think about any characters you are going to send out via the TX part of the UART ( output will take often the same time as input so it will invade the performance needed to get back to receiving the next character).
Next think about your code that will be executed between the receipt of Characters on the RX part of the UART.
Next imagine when looking at any line of your code (or any part of a line for that matter) that a character might be received while that line is executing. Be vigilant about printf and float math operations since they consume significant time relative to the time between transmission of inbound characters.
Once you have done all that you will probably realize that unless the code in main uses little time relative to the receive time of an inbound character
you will need to look into an interrupt driven receive buffer or alternatively some inbound flow control with your external device( Ex PC)
With the PC the OS drivers automatically provide extensive interrupt driven buffers so some PC programmers are unaware that they are often essential to reliable RS232 communication.
Cutting and pasting code from others can be efficient providing you have good knowledge of what the code is doing. If not it is like cutting and pasting phrases from the newspaper hoping to get Shakespear's Romeo and Juliet. It's possible it might work but very improbable. |
|
|
Guest
|
|
Posted: Thu Jun 29, 2006 2:46 pm |
|
|
PCM programmer wrote: | There's an edit function if you register and create a user name.
Then just select the option to automatically log you in.
Try the simple program shown below. Put a breakpoint on the
"delay_cycles(1)" line, and put the variable 'c' in a Watch window.
I don't have this PIC, but I tested it with vs. 3.249 with a 16F877
and it did work. When it breaks, you will see that 'c' is the value
that you typed in on the keyboard (displayed in hex format).
You didn't say what version of the compiler you have. I looked
at the .LST file for vs. 3.249 for the program below, and it looks OK.
I did notice there's a bug in the start-up code, that's probably been
there a long time. If you do a soft UART and you only specify the
'rcv' pin, the compiler inserts start-up code to set pin A0 as a low
level output pin -- in other words, it defaults to configuring Pin A0
as the 'xmit' pin even though you didn't specify it. That's why I
added Pin C2 as the xmit pin. If another pin is more convenient,
then change it to that pin. You can't use pins A0 or A1 because
those are used by the debugger.
Code: |
#include <16F684.h>
#fuses INTRC_IO, NOWDT, PUT, BROWNOUT
#use delay(clock=8000000)
#use rs232(baud=1200, xmit=PIN_C2, rcv=PIN_A2)
//===================================
void main(void)
{
char c;
while(1)
{
c = getc();
delay_cycles(1);
}
} |
If this doesn't work, then post your compiler version.
----------------------------
Also, it's a lot easier and more fun to test a serial port program
if you can echo the received characters back to your PC.
(Instead of setting a breakpoint and using a Watch window).
So consider using the following program instead:
Code: | #include <16F684.h>
#fuses INTRC_IO, NOWDT, PUT, BROWNOUT
#use delay(clock=8000000)
#use rs232(baud=1200, xmit=PIN_C2, rcv=PIN_A2)
//===================================
void main(void)
{
char c;
while(1)
{
c = getc(); // Wait for a char from the PC
putc(c); // Then send it back to the PC
}
} |
|
I'm tried your first bit of code on its own. I'm using compiler v3.209. The target application board that I'm coding for does not have a TX (it only needs to Rcv) so I can't use put(). I assigned xmit to be an 'unused' pin, basically on the target board pins A4 and A5 just have a 10k resistor to ground. They dont need to do anything, I suspect they may be there to use as a debug tool (i.e. set the pin high or low and watch with a scope when trying to debug something).
Anyway, the problem with the first bit of code you posted is I don't understand what the breakpoint at the delay_cycles(1) line is suppose to be. By setting the breakpoint there, as soon as I hit Run, it is instantly at the breakpoint. There's no time for me to have it running and waiting for a start bit and switch over to Hyperterminal to send a character.
Thats why it was nice with the ASM routines I used sicne I could set a breakpoint right at getChar(), then when I typed something it would interrupt and receive the character then be back at the breakpoint. So it only could do one char at a time, which is why I need to do it in C since I don't feel I could do the whole project in ASM (as far as doing arrays and crap).
So I believe I'll need to do it using a interrupt on the falling edge of RA2., whether or not I #use rs232. But I'm not getting #use rs232 to work at all.
Even by making the xmit pin to be anything, my PORTA still isn't showing 1 on RA2, even though I've confirmed with a meter and scope that RA2 is set high. So it doesn't seem to interrupt on the falling edge when I type something if it thinks it is already low. |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
Posted: Thu Jun 29, 2006 3:31 pm |
|
|
Quote: | Even by making the xmit pin to be anything, my PORTA still isn't
showing 1 on RA2, even though I've confirmed with a meter and scope
that RA2 is set high.
As soon as I hit Run, it is instantly at the breakpoint |
This implies that the PIC sees a constant start bit. This could be
caused by incorrect incoming levels (i.e., inverted) or it could be
that pin A2 is not configured as a digital i/o pin. (ie., it would read
constant 0 if it was configured as an analog pin).
Quote: | So I believe I'll need to do it using a interrupt on the falling edge
of RA2., whether or not I #use rs232. But I'm not getting #use rs232 to
work at all. |
This isn't the best way to solve a problem. If a relatively simple method
fails, then it should be analyzed down to the finest detail until the problem
is found. You shouldn't switch to a more complicated method. What
if there is a fundamental problem that will cause all methods to fail ?
That problem must be solved first.
Quote: | I'm using compiler v3.209. |
I installed your version and looked at the .LST file. The start-up code
is incorrect for your version. It's setting up the CMCON and ANSEL
registers incorrectly. Pin A2 is being setup as an analog pin.
This explains why nothing would work.
You can fix this by adding the lines shown in bold below:
Quote: |
#include <16F684.h>
#fuses INTRC_IO, NOWDT, PUT, BROWNOUT
#use delay(clock=8000000)
#use rs232(baud=1200, xmit=PIN_C2, rcv=PIN_A2)
//===================================
void main(void)
{
char c;
setup_comparator(NC_NC_NC_NC);
setup_adc_ports(NO_ANALOGS);
while(1)
{
c = getc();
delay_cycles(1);
// putc(c);
}
} |
|
|
|
bwgames
Joined: 21 Jul 2005 Posts: 36
|
|
Posted: Fri Jun 30, 2006 3:58 am |
|
|
I'm having a similar issue.
Using a PIC18F452, with a software UART connected to a MAX232 which goes to PC.
The minimum clock speed I could get working for the software UART was 115200, so I'm using that for the PC, and connected a RS232 input to the hardware UART (at 19200). That worked fine (PC received data in) from the hardware UART which was copied to the software. Note: Nothing was transmitted by PC.
I tried this code:
Code: |
//******************************************************************************
// INCLUDE FILES
#define Fosc 40000000 // I'm using a 40 MHz crystal
#include <18F452.h>
#device *=16 ADC=8
//-------------------------------------------------------------------------------------
// DEFINES
#define WireTX PIN_B0
#define WireRX PIN_B1
//-------------------------------------------------------------------------------------
// COMPILER DIRECTIVES and HARDWARE CONFIGURATION
#use delay(clock = Fosc)
#fuses EC_IO // EC Oscillator with RA6 configured as DIO
#fuses NOOSCSEN // Oscillator System Clock Switch Disabled
#fuses NODEBUG // No Background Debugger
#fuses NOLVP // Low Voltage ICSP Disabled
#fuses NOPROTECT // No Code Protect
#fuses NOWDT // No onboard watchdog
#fuses PUT // Power Up Timer Enabled
#fuses BROWNOUT // Brown Out Reset enabled
#use rs232(baud=19200, xmit=PIN_C6, rcv=PIN_C7, ERRORS, STREAM=Wireless) //Hardware UART
#use rs232(baud=115200, xmit=WireTX, rcv=WireRX, ERRORS, STREAM=Computer) //Software UART
//-------------------------------------------------------------------------------------
// GLOBALS
char c;
//-------------------------------------------------------------------------------------
// MAIN PROGRAM & SETUP
void main()
{
fprintf(Computer,"Receiver online\n\r\n\r");
while(TRUE) {
c = fgetc(Computer);
fputc(c, Computer);
}
} //End of main
|
yet I get garbage characters out....?
The fprintf(Computer,"Receiver online\n\r\n\r"); line works fine, I get that printed. |
|
|
Ttelmah Guest
|
|
Posted: Sun Jul 02, 2006 2:38 am |
|
|
Minimum!...
Do you mean maximum?.... The software UART, is completely dependant on counting instructions to do timings. It's error rate, on these tiings, rises as you push faster rates. Even with a 40MHz clock, 115200bps, only gives 86.8 instructions per bit time, and 43.4 instructions per half bit. The software UART, has to detect the falling edge of the start bit, then delay for 1.5 bit times, then sample the bit, store this, then delay for a bit time, sample, store this, for each bit in the character. The accuracy of this declines the faster the rate, hence normally it is the maximumm that is limited, rather than the minimum.
Now you don't say what test you are using on the code as shown?. The big problem with the software UART, is that the chip can only do one thing at a time (send or receive). With the hardware UART, both things can be occuring at once. Hence the code _must_ actually be 'waiting' for the character to arrive, otherwise straight away, there will be a tming error relative to the falling edge, and then once the character is received, and the reply is being sent, _no character may arrive_, till the transmission is complete. If a character starts to arrive, while the transmission is occuring, then what will be seen for this, will be garbage. Effectively the software UART, is 'half duplex', while the hardware unit, supports 'full duplex'.
Best Wishes |
|
|
bwgamess Guest
|
|
Posted: Mon Jul 03, 2006 7:31 am |
|
|
I mean minimum. I had timing errors (characters shifted), that only gave me unscrambled output at 115200.
9600 was total garbage
57600, made some sense
115200, perfect output.
To get the above figures I was just reading in on the hardware UART at 19200 and outputting on the software UART.
Thanks for that info about the software UART being half-duplex, I hadn't really given that enough consideration...
At data rates on the software UART below 115200, I get garbage. I am using a USB-RS232 convertor, might this be a reason why? |
|
|
|
|
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
|