Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Single pulse with variable length
#1
I need to generate a single pulse from EQU with a variable time. I thought I would use background real-time C program to do that because of the time I need, between 5usec to 10msec.

I put together a test program, see below. My program seems to work reliably starting from ~25usec and up. The smallest pulse I could get is ~15usec but jumps around between ~15usec to ~35usec when I set my delay to below 25usec.

Below is the sample of my code;

#define EQUWRITE_1 0xC0
#define EQUWRITE_0 0x40

int main(void)
{
struct sched_param param;
volatile GateArray3 *Gate3IC;
struct timespec sleeptime = {0};

param.__sched_priority = 50;
pthread_setschedparam(pthread_self(), SCHED_FIFO, &param);

InitLibrary();
Gate3IC = GetGate3MemPtr(0);

sleeptime.tv_nsec = 10000; // 10usec
Gate3IC->Chan[chanIdx].OutCtrl = Gate3IC->Chan[chanIdx].OutCtrl | EQUWRITE_1; // set EQU state to 1
nanosleep(&sleeptime, NULL);
Gate3IC->Chan[chanIdx].OutCtrl = Gate3IC->Chan[chanIdx].OutCtrl & ~EQUWRITE_1;
Gate3IC->Chan[chanIdx].OutCtrl = Gate3IC->Chan[chanIdx].OutCtrl | EQUWRITE_0; // set EQU state to 0

CloseLibrary();
return 0;
}

I did not try changing the sched_priority value as I am not sure if it is okay to go below 50 (that I got from Delta Tau IDE sample code).

What is the best 'time' resolution I could get from this background program?
Is there any other way besides using this technique to accomplish what I need?
Reply
#2
Probably the capture/compare interrupt service routine (ISR) is what you want. You could preload all of the on/off locations in the user buffer and then run a virtual motor to trigger all of the edges. That would be better than timing the on/off and would have no jitter.

What are you trying to achieve actually? Just need to pulse a device occasionally? What kind of pulse frequency? Does the "on-time" need to vary or is it constant? Give me a few more details and I can make you a more concrete example.
Reply
#3
(04-19-2016, 04:55 PM)CharlesP Wrote: Probably the capture/compare interrupt service routine (ISR) is what you want. You could preload all of the on/off locations in the user buffer and then run a virtual motor to trigger all of the edges. That would be better than timing the on/off and would have no jitter.

What are you trying to achieve actually? Just need to pulse a device occasionally? What kind of pulse frequency? Does the "on-time" need to vary or is it constant? Give me a few more details and I can make you a more concrete example.
Reply
#4
Thanks Charles.

Yes I am aware of using ISR routine which is supported only for Gate3. I do have a need for Gate2 as well but for now, I will focus on Gate3 only.

When you said about using virtual motor to trigger the edges, where do I do that? Will that be in virtual motor's user-servo routine? The way I understood from the manual(s), I would setup the COMPA/COMPB value for the channel (assuming motor #3) that will generate the pulses and implement the user-servo routine for the virtual motor (motor #2) which updates the motor #3's position. Is this what you are saying? If so, the rate of motor #2's user-servo routine call will depend on the servo-period (ServoClockDiv) for the system so I am limited to that frequency. For example, my system is currently have ServoClockDiv set to ~2.25K due to the hardware (I think), which only gives me ~444us. My minimum pulse width requirement is 5usec at least to be compatible with what we have today and of course the smaller we could get the better.

I need to be able to generate pulse(s) with or without moving my XYZ Stage. My understanding is I will use EQU (CompA/CompB/CompAdd) logic which will require somewhere (user-servo or user-phase routine) to update the position of the motor that is assigned to the EQU channel.

Thanks.
Reply
#5
No, it does not use user servo at all.

Set up a virtual motor with simulated servo loop and internal pulse-and-direction feedback. This will use up one encoder channel. Do you have one free channel on your Gate3? If so, you can do this. It will work just like normal position compare except virtual, and you do not need to move any physical motors. By offloading this processing to the Gate3 chip, you get up to multiple MHz sampling rate for your compare edges and the ISR can run at something like 80 kHz.

I will post the settings for this in a little bit -- I am tied up with something at the moment.

The last part is to preload all of your edges in Sys.Idata[] elements. Then when an edge is reached, load in the next set in the ISR, just like the example on page 759 of the PPMAC User's Manual. You of course only need to do this if the pulse width is variable -- otherwise, the ISR is not needed, and you can just use the auto-increment feature of position compare.
Reply
#6
Here are settings you can use for a virtual motor. I'm posting settings for 4 channels, so you can just take the settings corresponding to the channel you want to use.

/* Gate3 Version */
Sys.WpKey = $AAAAAAAA // Permit write access to Gate3 structures

/* Activate motors 1-4 */
Motor[1].ServoCtrl=1
Motor[2].ServoCtrl=1
Motor[3].ServoCtrl=1
Motor[4].ServoCtrl=1

// Write servo output to channel’s PFM register
Motor[1].pDac=Gate3[0].Chan[0].Pfm.a // 1st channel PFM
Motor[2].pDac=Gate3[0].Chan[1].Pfm.a // 2nd channel PFM
Motor[3].pDac=Gate3[0].Chan[2].Pfm.a // 3rd channel PFM
Motor[4].pDac=Gate3[0].Chan[3].Pfm.a // 4th channel PFM

// Set PFM Clock Divisor
Gate3[0].PfmClockDiv=5


// Set up channel output for PFM signal
// Not needed for simulation; only to monitor output signal
Gate3[0].Chan[0].OutputMode=8 // PFM on Phase D
Gate3[0].Chan[1].OutputMode=8 // PFM on Phase D
Gate3[0].Chan[2].OutputMode=8 // PFM on Phase D
Gate3[0].Chan[3].OutputMode=8 // PFM on Phase D

// Wrap PFM output back into channel’s encoder counter
Gate3[0].Chan[0].EncCtrl=8 // Internal pulse and direction clockwise
Gate3[0].Chan[1].EncCtrl=8 // Internal pulse and direction clockwise
Gate3[0].Chan[2].EncCtrl=8 // Internal pulse and direction clockwise
Gate3[0].Chan[3].EncCtrl=8 // Internal pulse and direction clockwise

// Disable overtravel limit inputs
// May be needed if there are no physical switches present
Motor[1].pLimits=0
Motor[2].pLimits=0
Motor[3].pLimits=0
Motor[4].pLimits=0

// Disable amplifier enable output
// May be needed if channel is also connected to real amplifier
Motor[1].pAmpEnable=0
Motor[2].pAmpEnable=0
Motor[3].pAmpEnable=0
Motor[4].pAmpEnable=0

// Disable amplifier fault input
// May be needed if channel is also connected to real amplifier
Motor[1].pAmpFault=0
Motor[2].pAmpFault=0
Motor[3].pAmpFault=0
Motor[4].pAmpFault=0

// Set derivative gain term in servo loop to zero
// This is a Type 1 servo (single integration); does not need Kvfb
Motor[1].Servo.Kvfb=0
Motor[2].Servo.Kvfb=0
Motor[3].Servo.Kvfb=0
Motor[4].Servo.Kvfb=0

// Lower proportional gain term from default
Motor[1].Servo.Kp=40
Motor[2].Servo.Kp=40
Motor[3].Servo.Kp=40
Motor[4].Servo.Kp=40

// Add Kvff and Ki
Motor[1].Servo.Kvff=40;
Motor[2].Servo.Kvff=40;
Motor[3].Servo.Kvff=40;
Motor[4].Servo.Kvff=40;

Motor[1].Servo.Ki=0.001;
Motor[2].Servo.Ki=0.001;
Motor[3].Servo.Ki=0.001;
Motor[4].Servo.Ki=0.001;

// Deactivate commutation for all motors
Motor[0].PhaseCtrl=0 // No phase commutation active
Motor[2].PhaseCtrl=0 // No phase commutation active
Motor[3].PhaseCtrl=0 // No phase commutation active
Motor[4].PhaseCtrl=0 // No phase commutation active

// Create encoder conversion table entries for all motors
EncTable[1].type=1;
EncTable[1].pEnc=Gate3[0].Chan[0].ServoCapt.a;
EncTable[1].pEnc1=Sys.pushm;
EncTable[1].index1=0;
EncTable[1].index2=0;
EncTable[1].index3=0;
EncTable[1].index4=0;
EncTable[1].ScaleFactor=1.0/256.0;
EncTable[1].MaxDelta=0;
EncTable[1].SinBias=0;
EncTable[1].CosBias=0;

EncTable[2].type=1;
EncTable[2].pEnc=Gate3[0].Chan[1].ServoCapt.a;
EncTable[2].pEnc1=Sys.pushm;
EncTable[2].index1=0;
EncTable[2].index2=0;
EncTable[2].index3=0;
EncTable[2].index4=0;
EncTable[2].ScaleFactor=1.0/256.0;
EncTable[2].MaxDelta=0;
EncTable[2].SinBias=0;
EncTable[2].CosBias=0;

EncTable[3].type=1;
EncTable[3].pEnc=Gate3[0].Chan[2].ServoCapt.a;
EncTable[3].pEnc1=Sys.pushm;
EncTable[3].index1=0;
EncTable[3].index2=0;
EncTable[3].index3=0;
EncTable[3].index4=0;
EncTable[3].ScaleFactor=1.0/256.0;
EncTable[3].MaxDelta=0;
EncTable[3].SinBias=0;
EncTable[3].CosBias=0;

EncTable[4].type=1;
EncTable[4].pEnc=Gate3[0].Chan[3].ServoCapt.a;
EncTable[4].pEnc1=Sys.pushm;
EncTable[4].index1=0;
EncTable[4].index2=0;
EncTable[4].index3=0;
EncTable[4].index4=0;
EncTable[4].ScaleFactor=1.0/256.0;
EncTable[4].MaxDelta=0;
EncTable[4].SinBias=0;
EncTable[4].CosBias=0;

// Point motors to encoder conversion table entries
Motor[1].pEnc=EncTable[1].a
Motor[2].pEnc=EncTable[2].a
Motor[3].pEnc=EncTable[3].a
Motor[4].pEnc=EncTable[4].a

Motor[1].pEnc2=EncTable[1].a
Motor[2].pEnc2=EncTable[2].a
Motor[3].pEnc2=EncTable[3].a
Motor[4].pEnc2=EncTable[4].a

/* Set up various PFM-Related Gate3 settings */
Gate3[0].Chan[0].OutFlagD=1;
Gate3[0].Chan[1].OutFlagD=1;
Gate3[0].Chan[2].OutFlagD=1;
Gate3[0].Chan[3].OutFlagD=1;

Gate3[0].chan[0].packoutdata=0
Gate3[0].chan[0].packindata=0
Gate3[0].Chan[0].PfmWidth=10 // Can be adjusted for smaller pulse width

Gate3[0].chan[1].packoutdata=0
Gate3[0].chan[1].packindata=0
Gate3[0].Chan[1].PfmWidth=10 // Can be adjusted for smaller pulse width

Gate3[0].chan[2].packoutdata=0
Gate3[0].chan[2].packindata=0
Gate3[0].Chan[2].PfmWidth=10 // Can be adjusted for smaller pulse width

Gate3[0].chan[3].packoutdata=0
Gate3[0].chan[3].packindata=0
Gate3[0].Chan[3].PfmWidth=10 // Can be adjusted for smaller pulse width

Sys.WpKey=$0 // Write-protect Gate3 registers
Reply
#7
Here is an alternate set of virtual servo loop gains in case you cannot get the first set to work:

// Set derivative gain term in servo loop to zero
// This is a Type 1 servo (single integration); does not need Kd
Motor[1].Servo.Kvfb=0
Motor[2].Servo.Kvfb=0
Motor[3].Servo.Kvfb=0
Motor[4].Servo.Kvfb=0

// Lower proportional gain term from default
Motor[1].Servo.Kp=1
Motor[2].Servo.Kp=1
Motor[3].Servo.Kp=1
Motor[4].Servo.Kp=1

// Add Kvff and Ki
Motor[1].Servo.Kvff=1.5;
Motor[2].Servo.Kvff=1.5;
Motor[3].Servo.Kvff=1.5;
Motor[4].Servo.Kvff=1.5;

Motor[1].Servo.Ki=0.001;
Motor[2].Servo.Ki=0.001;
Motor[3].Servo.Ki=0.001;
Motor[4].Servo.Ki=0.001;

Motor[1].Servo.Kbreak=0; // zero gain in deadband area
Motor[1].Servo.BreakPosErr=1; // deadband of 1 count should keep stepper driver from coming out of standby power mode

Motor[2].Servo.Kbreak=0; // zero gain in deadband area
Motor[2].Servo.BreakPosErr=1; // deadband of 1 count should keep stepper driver from coming out of standby power mode

Motor[3].Servo.Kbreak=0; // zero gain in deadband area
Motor[3].Servo.BreakPosErr=1; // deadband of 1 count should keep stepper driver from coming out of standby power mode

Motor[4].Servo.Kbreak=0; // zero gain in deadband area
Motor[4].Servo.BreakPosErr=1; // deadband of 1 count should keep stepper driver from coming out of standby power mode
Reply
#8
(04-20-2016, 09:44 AM)CharlesP Wrote: Here are settings you can use for a virtual motor. I'm posting settings for 4 channels, so you can just take the settings corresponding to the channel you want to use.

/* Gate3 Version */
Sys.WpKey = $AAAAAAAA // Permit write access to Gate3 structures

/* Activate motors 1-4 */
Motor[1].ServoCtrl=1
Motor[2].ServoCtrl=1
Motor[3].ServoCtrl=1
Motor[4].ServoCtrl=1

// Write servo output to channel’s PFM register
Motor[1].pDac=Gate3[0].Chan[0].Pfm.a // 1st channel PFM
Motor[2].pDac=Gate3[0].Chan[1].Pfm.a // 2nd channel PFM
Motor[3].pDac=Gate3[0].Chan[2].Pfm.a // 3rd channel PFM
Motor[4].pDac=Gate3[0].Chan[3].Pfm.a // 4th channel PFM

// Set PFM Clock Divisor
Gate3[0].PfmClockDiv=5


// Set up channel output for PFM signal
// Not needed for simulation; only to monitor output signal
Gate3[0].Chan[0].OutputMode=8 // PFM on Phase D
Gate3[0].Chan[1].OutputMode=8 // PFM on Phase D
Gate3[0].Chan[2].OutputMode=8 // PFM on Phase D
Gate3[0].Chan[3].OutputMode=8 // PFM on Phase D

// Wrap PFM output back into channel’s encoder counter
Gate3[0].Chan[0].EncCtrl=8 // Internal pulse and direction clockwise
Gate3[0].Chan[1].EncCtrl=8 // Internal pulse and direction clockwise
Gate3[0].Chan[2].EncCtrl=8 // Internal pulse and direction clockwise
Gate3[0].Chan[3].EncCtrl=8 // Internal pulse and direction clockwise

// Disable overtravel limit inputs
// May be needed if there are no physical switches present
Motor[1].pLimits=0
Motor[2].pLimits=0
Motor[3].pLimits=0
Motor[4].pLimits=0

// Disable amplifier enable output
// May be needed if channel is also connected to real amplifier
Motor[1].pAmpEnable=0
Motor[2].pAmpEnable=0
Motor[3].pAmpEnable=0
Motor[4].pAmpEnable=0

// Disable amplifier fault input
// May be needed if channel is also connected to real amplifier
Motor[1].pAmpFault=0
Motor[2].pAmpFault=0
Motor[3].pAmpFault=0
Motor[4].pAmpFault=0

// Set derivative gain term in servo loop to zero
// This is a Type 1 servo (single integration); does not need Kvfb
Motor[1].Servo.Kvfb=0
Motor[2].Servo.Kvfb=0
Motor[3].Servo.Kvfb=0
Motor[4].Servo.Kvfb=0

// Lower proportional gain term from default
Motor[1].Servo.Kp=40
Motor[2].Servo.Kp=40
Motor[3].Servo.Kp=40
Motor[4].Servo.Kp=40

// Add Kvff and Ki
Motor[1].Servo.Kvff=40;
Motor[2].Servo.Kvff=40;
Motor[3].Servo.Kvff=40;
Motor[4].Servo.Kvff=40;

Motor[1].Servo.Ki=0.001;
Motor[2].Servo.Ki=0.001;
Motor[3].Servo.Ki=0.001;
Motor[4].Servo.Ki=0.001;

// Deactivate commutation for all motors
Motor[0].PhaseCtrl=0 // No phase commutation active
Motor[2].PhaseCtrl=0 // No phase commutation active
Motor[3].PhaseCtrl=0 // No phase commutation active
Motor[4].PhaseCtrl=0 // No phase commutation active

// Create encoder conversion table entries for all motors
EncTable[1].type=1;
EncTable[1].pEnc=Gate3[0].Chan[0].ServoCapt.a;
EncTable[1].pEnc1=Sys.pushm;
EncTable[1].index1=0;
EncTable[1].index2=0;
EncTable[1].index3=0;
EncTable[1].index4=0;
EncTable[1].ScaleFactor=1.0/256.0;
EncTable[1].MaxDelta=0;
EncTable[1].SinBias=0;
EncTable[1].CosBias=0;

EncTable[2].type=1;
EncTable[2].pEnc=Gate3[0].Chan[1].ServoCapt.a;
EncTable[2].pEnc1=Sys.pushm;
EncTable[2].index1=0;
EncTable[2].index2=0;
EncTable[2].index3=0;
EncTable[2].index4=0;
EncTable[2].ScaleFactor=1.0/256.0;
EncTable[2].MaxDelta=0;
EncTable[2].SinBias=0;
EncTable[2].CosBias=0;

EncTable[3].type=1;
EncTable[3].pEnc=Gate3[0].Chan[2].ServoCapt.a;
EncTable[3].pEnc1=Sys.pushm;
EncTable[3].index1=0;
EncTable[3].index2=0;
EncTable[3].index3=0;
EncTable[3].index4=0;
EncTable[3].ScaleFactor=1.0/256.0;
EncTable[3].MaxDelta=0;
EncTable[3].SinBias=0;
EncTable[3].CosBias=0;

EncTable[4].type=1;
EncTable[4].pEnc=Gate3[0].Chan[3].ServoCapt.a;
EncTable[4].pEnc1=Sys.pushm;
EncTable[4].index1=0;
EncTable[4].index2=0;
EncTable[4].index3=0;
EncTable[4].index4=0;
EncTable[4].ScaleFactor=1.0/256.0;
EncTable[4].MaxDelta=0;
EncTable[4].SinBias=0;
EncTable[4].CosBias=0;

// Point motors to encoder conversion table entries
Motor[1].pEnc=EncTable[1].a
Motor[2].pEnc=EncTable[2].a
Motor[3].pEnc=EncTable[3].a
Motor[4].pEnc=EncTable[4].a

Motor[1].pEnc2=EncTable[1].a
Motor[2].pEnc2=EncTable[2].a
Motor[3].pEnc2=EncTable[3].a
Motor[4].pEnc2=EncTable[4].a

/* Set up various PFM-Related Gate3 settings */
Gate3[0].Chan[0].OutFlagD=1;
Gate3[0].Chan[1].OutFlagD=1;
Gate3[0].Chan[2].OutFlagD=1;
Gate3[0].Chan[3].OutFlagD=1;

Gate3[0].chan[0].packoutdata=0
Gate3[0].chan[0].packindata=0
Gate3[0].Chan[0].PfmWidth=10 // Can be adjusted for smaller pulse width

Gate3[0].chan[1].packoutdata=0
Gate3[0].chan[1].packindata=0
Gate3[0].Chan[1].PfmWidth=10 // Can be adjusted for smaller pulse width

Gate3[0].chan[2].packoutdata=0
Gate3[0].chan[2].packindata=0
Gate3[0].Chan[2].PfmWidth=10 // Can be adjusted for smaller pulse width

Gate3[0].chan[3].packoutdata=0
Gate3[0].chan[3].packindata=0
Gate3[0].Chan[3].PfmWidth=10 // Can be adjusted for smaller pulse width

Sys.WpKey=$0 // Write-protect Gate3 registers

Thanks Charles.

At the moment, I am having trouble with the CaptCompISR() routine (see below) causing disconnect to PowerPMAC when trying to exercise with homing routine. I will continue with the laser pulsing function as soon as I get the disconnect problem resolved.

void CaptCompISR(void)
{
volatile GateArray3 *MyGate3; // ASIC structure pointer
int *CaptCounter; // Logs number of triggers
int Temp;
MyGate3 = GetGate3MemPtr(0); // Pointer to IC base
CaptCounter = (int *)pushm + 65535; // Sys.Idata[65535]
(*CaptCounter)++; // Increment counter
Temp = MyGate3->Chan[0].HomeCapt;
MyGate3->IntCtrl = 1;
}
Reply
#9
This works fine for me -- what is your Gate3[i].IntCtrl setting?

I just tested it with the following:

// Script command to set up interrupt on first channel capture
Gate3[0].IntCtrl = $10000 // Unmask PosCapt[0] (not saved)
// Script command to initialize trigger counter
Sys.Idata[65535] = 0
// Script command to enable capture/compare ISR
UserAlgo.CaptCompIntr = 1

Also what firmware are you using? I tested it with 2.0.2.291
Reply
#10
(04-20-2016, 02:03 PM)CharlesP Wrote: This works fine for me -- what is your Gate3[i].IntCtrl setting?

I just tested it with the following:

// Script command to set up interrupt on first channel capture
Gate3[0].IntCtrl = $10000 // Unmask PosCapt[0] (not saved)
// Script command to initialize trigger counter
Sys.Idata[65535] = 0
// Script command to enable capture/compare ISR
UserAlgo.CaptCompIntr = 1

Also what firmware are you using? I tested it with 2.0.2.291

Thanks Charles.
I did do the same as what you have shown here as they came right out of the manual. According to the response of 'ver' command, the firmware version is 2.0.2.14.
Reply
#11
Not sure then. I suggest you try with a "clean," new project and try that code alone. Maybe something else "hiding" in your project caused the problem?

You can spin the motor's encoder by hand even after setting Motor[x].ServoCtrl=1 and you should see counts if it is a standard digital quadrature encoder.
Reply
#12
(04-21-2016, 09:51 AM)CharlesP Wrote: Not sure then. I suggest you try with a "clean," new project and try that code alone. Maybe something else "hiding" in your project caused the problem?

You can spin the motor's encoder by hand even after setting Motor[x].ServoCtrl=1 and you should see counts if it is a standard digital quadrature encoder.

Thanks Charles.
I did send my project to Delta Tau support yesterday although I am not sure if it is the cause. For now, I plan to just manually move the axis to trigger the home switch with UserAlgo.CaptCompIntr set to 1 to see if makes any difference.
Reply
#13
Here is another fault when using CaptCompISR to prepare data for EQU0 on ACC24E3.

It seems that calling ISR would cause Sys.WDTFault=2, Sys.ServoCount stop growing and Gate3[0].IntCtrl=$ffffffff. I try this by set "Gate3[0].IntCtrl |=$100000" "UserAlgo.CaptCompIntr=1" and "Gate3[0].Chan[0].EquWrite=3" in terminal, with default code in CaptCompISR routine.

It is said Gate3[i].IntCtrl=0 by default, but is $f00 on my device.

Is it limited by firmware? Power Umac firmware v2.0.2.14.
Reply
#14
A software watchdog timer fault will put the PMAC backplane into a locked reset state. In this state most gate element bits will “float” to “1” (Gate3[0].IntCtrl=$ffffffff). The cause of this would be dependent on the code in the ISR.

The value of Gate3[i].IntCtrl = $f00 may be the result of encoder capture occurrence on all channels of the gate.

We would need more details on your code and other settings (full project after a PMAC ‘save’ would be best). Contact the ODT Technical Support Group at support@deltatau.com with these details.

This would not be related to firmware version.
Reply
#15
(06-26-2018, 04:23 PM)steve.milici Wrote: A software watchdog timer fault will put the PMAC backplane into a locked reset state. In this state most gate element bits will “float” to “1” (Gate3[0].IntCtrl=$ffffffff). The cause of this would be dependent on the code in the ISR.

The value of Gate3[i].IntCtrl = $f00 may be the result of encoder capture occurrence on all channels of the gate.

We would need more details on your code and other settings (full project after a PMAC ‘save’ would be best). Contact the ODT Technical Support Group at support@deltatau.com with these details.

This would not be related to firmware version.

1. It is the default project created by IDE v3.1.4.0, only with build action of "usrcode.c" changed from "content" to "compile".
2. The software manual says the range of Gate3[i].IntCtrl is $0~$00FFFFFF, 24bits then. Why $ffffffff in my test?
3. This default Gate3[i].IntCtrl=$f00 means that all capture has occured? But the corresponding high byte of Gate3[i].IntCtrl have not been set yet.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)