PIC24F EPMP with EDS

General questions about Firewing...

Re: PIC24F EPMP with EDS

Postby Jerry Messina » Mon Dec 11, 2017 11:00 am

The clrwdt is likely coming from firewing (sys.imports\sysWDT.bas)

You could try adding the following to your main .bas file and see if that gets rid of it...
Code: Select all
' system options...
#sysoption WDT = false

' device and clock...
Device = 24FJ128GA204    'Select processor
clock = 32               'Define system clock

Compiler #options can only appear after the device and clock commands, if used.
If an #option must pass through to 'lib.imports' or 'sys.imports', then #sysoption should be used before any device or clock statement.
If no device or clock statement is used, #sysoption must appear before any #option.


It looks like MPSIM support might have some issues with that chip... it's shown in yellow for MPLAB 8.92

EDIT: adding the above doesn't seem to stop it... I had to change the 'clrwdt' to 'nop' in the file firewing\bin\toolsuite\microchip16\macros_delay.h
I don't know if there's a more "proper" way to do this...
Code: Select all
//****************************************************************************
//* Name    : __delay32                                                      *
//* Purpose : 32-bit cycle delay                                             *
//* Input   : w1:w0 = delay in cycles (min == 12, including call)            *
//* Output  : None                                                           *
//          : registers used w0,w1                                           *
//****************************************************************************

__attribute__((far)) void __Delay32(void)  {
   asm("sub  #1023,w0" ::: "w0");
   asm("subb #0,w1" ::: "w1");
   asm("bra  lt,2f");

asm("4:");
   asm("repeat #1006");
   //asm("clrwdt");
   asm("nop");
   asm("sub  #1012, w0" ::: "w0");
   asm("subb #0, w1" ::: "w1");
   asm("bra  ge, 4b");
   asm("add  #1,w0" ::: "w0");

asm("2:");
   asm("add  #1010,w0" ::: "w0");
   asm("bra  lt,3f");
   asm("repeat w0");
   //asm("clrwdt");
   asm("nop");
asm("3:");
};
Jerry Messina
 
Posts: 280
Joined: Thu Feb 14, 2013 10:16 am

Re: PIC24F EPMP with EDS

Postby jmusselman64 » Tue Dec 12, 2017 6:01 am

Got it to work in MPLAB SIM after changing CLRWDT to NOP. Wonder if I could add a test for the #SYSOPTION WDT to skip the CLRWDT if it's False? Maybe later, but first..

I had numerous problems that came to light once I got in the Sim.
First, I had mis-translated the WHILE blocking code in LCD SendCMD..it was always skipping it.
Next, I had the PMP Data lines reversed to the display, and I didn't change the FUNCTIONSET constant to 4BITMODE.
Finally, and where I'm stuck now, in 4-bit mode the LCD expects nibbles in MSB/LSB order. The EPMP only sends them in LSB/MSB order. I need to swap them.

I though I could just do an inline sub like:
Code: Select all
ASM
            MOV WREG0,TEMP
            SWAP.B  WREG0
             MOV TEMP,WREG0                       //swap byte
            End Asm

but GCC is complaining about 'invalid operands specified'
I couldn't find an easier way to swap nibbles in Firewing...any ideas?

Thanks so much for the help. I feel this is the last piece of the puzzle now!
Jerry
jmusselman64
 
Posts: 24
Joined: Thu Jan 22, 2015 1:01 am

Re: PIC24F EPMP with EDS

Postby Jerry Messina » Tue Dec 12, 2017 10:54 am

The asm syntax for SWAP is 'SWAP Wn', where n=0-15. It doesn't seem to like the 'WREG' alias for W0, so just use W0.

Maybe something like this would work...
Code: Select all
// swap nibbles in a byte
inline function swap(b as WREG0) as WREG0
   ASM
   SWAP.B  W0
   END ASM
end function

dim b as byte

b = swap(b)


You seem to have made pretty good headway, Jerry. Keep us posted on how it goes!
Jerry Messina
 
Posts: 280
Joined: Thu Feb 14, 2013 10:16 am

Re: PIC24F EPMP with EDS

Postby jmusselman64 » Thu Dec 14, 2017 5:05 am

SUCCESS!
First I added the SWAP function ( still confused on how the assignment 'b as WREG0' works..), then fixed the 'state' variable declaration. It was a STATIC in the C program; It's now a global in the module. All works great and timing is as expected.
So I started merging it with the original Firewing 'LCD' module to create the new EPMPLCD module, thinking all would be easy from here on..

For whatever reason, it's complaining about all the EPMP SFR's. I initially had an older version of the 24FJ128GA204.bas file and it said it didn't have a PMP. Weird, since it compiled the original program fine.
I tried re-assigning them by adding an underscore (similar to fix for the _DSWPAG register variable) and it started compiling, but I think it's getting stuck on the ' _PMCON1.15 = 1' line.

Is this because I no longer have a processor definition in the module?

Here's the module so far: (sorry.. the formatting went nuts in this window.)
Code: Select all
'********************************************************************************************************************
'*  Name    : EPMPLCD.BAS                                                                                              *
'*  Author  : Jerry Musselman                                                                                            *
'*  Notice  :                                                                                                            *
'*          :                                                                                                             *
'*  Date    : 12/13/2017                                                                                               *
'*  Version : 1.0                                                                                                      *
'*  Notes   : Utilizes the Enhanced Parralel Master Port on PIC24F processors                                          *
'*          : To control a standard HD44780 based character LCD module                                                *
'*          : Ported from  'C' code by OLEG MAZUROV at:                                                              *
'*          : https://www.circuitsathome.com/mcu/driving-a-character-lcd-using-pic24-enhanced-parallel-master-port/    *
'*          : Comments from the article have been edited/included into the program.                                    *
'*            : Merged/mutated with the original Firewing 'LCD' module by David John Barker                              *
'********************************************************************************************************************

Module EPMPLCD

/*
When developing for the HD44780 LCD display driver there are 3 different times to consider:
1) First is the timing of the display part – the screen we see. LCD glass is very slow.
When we attempt to update the screen faster than say twice a second the symbols become blurry and pale.

2) Display data bus timing is many times faster.
In order to write to the display we first need to set RS, RW and data lines, wait a little, then assert E line, wait some more and then de-assert it.
If we are reading from the display we will also need to wait a little more after de-asserting E before we can read the data on the bus.
Total bus cycle length is ~2.5us, which is 200 000 times less than the update rate of typical LCD glass.

3) The third timing we need to deal with is command execution time. All but two LCD commands have stated execution time of 40us.  Two slow commands – Clear and Home require 1.64ms to finish.
Those are datasheet numbers, in reality the fast command on a modern display may finish in as low as 10us and slow commands on an old display can take as much as 3.5ms,
depending on the age and the particular “HD44780-compatible” controller used.
*/

 /*
 Option section - if no values are given in the main program, this section
 will initialize them.
*/

// LCD interface...
#option LCD_INTERFACE = 4           // can be 4 or 8 bit interface
#if IsOption(LCD_INTERFACE) And Not (LCD_INTERFACE in (4, 8))
   #error LCD_INTERFACE, "Invalid option. LCD_INTERFACE must be either 4 or 8."
#endif

// SIZE
#option LCD_ROWS = 4         //default for 4x20 module
#option LCD_COLS = 20      //default for 4x20 module

// Standard DDRAM line offsets - should really only be set
// by other modules which build on this LCD library
#option LCD_LINE_2 = &H40
#option LCD_LINE_3 = &H14
#option LCD_LINE_4 = &H54

//----DEFINITIONS-------
#define LCD_TX_BUFSIZE = 256          //The size of the buffer must be a power of 2. The size of 256 saves couple instruction cycles; if memory size is more important the buffer size can be decreased.
#define LCD_TX_BUFMASK = (LCD_TX_BUFSIZE - 1)

#if (LCD_TX_BUFSIZE and LCD_TX_BUFMASK) = 1
   #error "LCD Tx Buffer size is not a power of 2"
#endif

private const LCDLine_2 = LCD_LINE_2
private const LCDLine_3 = LCD_LINE_3
private const LCDLine_4 = LCD_LINE_4

// Public commands
public const cmdCGRAM     = &B01000000
public const cmdDDRAM     = &B10000000

public const cmdClear     = &B00000001
public const cmdHome      = &B00000010
public const cmdCursorOff = &B00001100
public const cmdCursorOn  = &B00001110 
public const cmdBlinkOn   = &B00001101
public const cmdBlinkOff  = &B00001100
public const cmdMoveLeft  = &B00010000
public const cmdMoveRight = &B00010100

// LCD commands

const LCD_ENTRYMODESET =        &H04
const LCD_DISPLAYCONTROL =      &H08
const LCD_CURSORSHIFT =         &H10
const LCD_FUNCTIONSET =         &H20
const LCD_SETCGRAMADDR =        &H40
const LCD_SETDDRAMADDR =        &H80

// Flags for display entry mode
const LCD_ENTRYRIGHT=          &H00
const LCD_ENTRYLEFT=           &H02
const LCD_ENTRYSHIFTINCREMENT= &H01
const LCD_ENTRYSHIFTDECREMENT= &H00

// Flags for display on/off control
const LCD_DISPLAYON=           &H04
const LCD_DISPLAYOFF=          &H00
const LCD_CURSORON=            &H02
const LCD_CURSOROFF=           &H00
const LCD_BLINKON=             &H01
const LCD_BLINKOFF=            &H00

// Flags for display/cursor shift
const LCD_DISPLAYMOVE=         &H08
const LCD_CURSORMOVE=          &H00
const LCD_MOVERIGHT=           &H04
const LCD_MOVELEFT=            &H00

// Flags for function set
const LCD_8BITMODE=            &H10
const LCD_4BITMODE=            &H00
const LCD_2LINE =              &H08
const LCD_1LINE =              &H00
const LCD_5x10DOTS=            &H04
const LCD_5x8DOTS =            &H00

// Initialization commands for standard 16x2 LCD
const FUNC_SET as byte  = (LCD_FUNCTIONSET or LCD_4BITMODE or LCD_2LINE or LCD_5x8DOTS)
const DISP_CTRL as byte = (LCD_DISPLAYCONTROL or LCD_DISPLAYON or LCD_CURSOROFF or LCD_BLINKOFF)
const ENTRY_MODE as byte  = (LCD_ENTRYMODESET or LCD_ENTRYLEFT)

const TIMER3_ISR_PRIO = 1
const BSP_TMR3_PER_SHORT = 799        // Timer3 period for fast commands, (32 MHz)
const BSP_TMR3_PER_LONG = 35000      // Timer3 period for slow commands
const CMDFLAG = &H0f                 // This code inserted before a command in LCD queue
const CS_BASE = &H0002               // Start address for LCD registers in EDS

const lcd_init_seq(4) as byte  = {FUNC_SET, DISP_CTRL, LCD_CLEARDISPLAY, ENTRY_MODE}   //initialization sequence

private _posX, _posY As Byte        // local x, y coordinates 

public system _DSWPAG as ushort absolute DSWPAG    // do it this way to fix confusion in definition file

public system _PMCON1 as ushort absolute PMCON1
public system _PMCON2 as ushort absolute PMCON2
public system _PMCON3 as ushort absolute PMCON3
public system _PMCON4 as ushort absolute PMCON4
public system _PMCS1CF as ushort absolute PMCS1CF
public system _PMCS1BS as ushort absolute PMCS1BS
public system _PMCS1MD as ushort absolute PMCS1MD

public LCDCMD as Byte absolute &H8000               //set bit 15 to trigger EDS
public LCDALIGN as Byte absolute &H8001
public LCDDAT as Byte absolute &H8002

// Define LCD buffer
dim LcdTx_Buf(LCD_TX_BUFSIZE) as byte
dim LcdTx_Head as uinteger = 0                     // LcdTx_Head is moved by a producer of the data
dim LcdTx_Tail as uinteger = 0                     //LcdTx_Tail is moved by the consumer

dim state as byte = 0         //Used in Timer3 interrupt for command/data execution

//--------------SUBS------------------

/*
****************************************************************************
* Name    : WaitFor                                                        *
* Purpose : Wait for device busy flag to clear                             *
****************************************************************************
*/
private Sub WaitFor()
   do
   loop Until (ReadByte() And &H80) = 0
End Sub 

'****************************************************************************
'* Name    : SetDDRAM                                                       *
'* Purpose : Set DDRAM address                                              *
'****************************************************************************
private Sub SetDDRAM(address As Byte)
   
   #if LCD_INTERFACE = 8
   LCDSendByte(address Or &B10000000)
   #else
   LCDSendByte((address Or &B10000000) >> 4)
   LCDSendByte(address)
   #endif 

   ClearWDT()
End Sub

'****************************************************************************
'* Name    : WriteCommandAsByte                                             *
'* Purpose : Write byte command to LCD                                      *
'****************************************************************************
private Sub WriteCommandAsByte(cmd as byte) 
   LcdSendByte(CMDFLAG)         //insert command flag code
   LcdSendByte(cmd)         
   ClearWDT()
End Sub

'****************************************************************************
'* Name    : SetCursor                                                      *
'* Purpose : Set the cursor to line and column                              *
'****************************************************************************
Public Sub SetCursor(line As Byte, column As Byte)

#if line =  0 or line >LCD_ROWS then      //Range check row#
   #error "Invalid LCD row number!"
#endif

#if column = 0 or column > LCD_COLS then   //Range check column#
   #error "Invalid LCD Column number!"
#endif
   column -= 1                  // correct for zero-offset
   Select line
      Case 1 : SetDDRAM(column)
      Case 2 : SetDDRAM(LCDLine_2 + column)
      Case 3 : SetDDRAM(LCDLine_3 + column)
      Case 4 : SetDDRAM(LCDLine_4 + column)
   End Select
End Sub

'****************************************************************************
'* Name    : WriteItem                                                      *
'* Purpose : Write a single byte to the LCD                                 *
'****************************************************************************
private overloads Sub WriteItem(data As Byte)
   LcdSendByte(data)
   ClearWDT()
End Sub

'****************************************************************************
'* Name    : WriteItem                                                      *
'* Purpose : Write a string to the LCD                                      *
'****************************************************************************
private overloads Sub WriteItem(text As String)
   addr0 = AddressOf(text)
   While *(addr0) <> 0
      WriteItem(*(addr0+))
   End While
End Sub

'****************************************************************************
'* Name    : WriteItem                                                      *
'* Purpose : Write a string to the LCD                                      *
'****************************************************************************
private overloads Sub WriteItem(ByRef text As String Absolute addr0)
   While *(addr0) <> 0
      WriteItem(*(addr0+))
   End While
End Sub

'****************************************************************************
'* Name    : WriteItem                                                      *
'* Purpose : Initialize the CGRAM with constant data array. Eight           *
'*         : programmable characters are available (0..7).                  *
'****************************************************************************
private overloads Sub WriteItem(ByRefConst bitmap() As Byte)
   LcdSendCmd(cmdCGRAM)
   For Index As Byte = LBound(bitmap) To UBound(bitmap)
      WriteItem(bitmap(Index))
   Next Index
   LcdSendCmd(cmdDDRAM)
End Sub

'****************************************************************************
'* Name    : Write                                                          *
'* Purpose : Calls one or more WriteItem() subroutines                      *
'****************************************************************************
Public Compound Sub Write(WriteItem)

'****************************************************************************
'* Name    : MoveTo                                                         *
'* Purpose : Moves cursor to module private x, y location                   *
'****************************************************************************
private Inline Sub MoveTo()
   SetCursor(_posY, _posX)
End Sub

'****************************************************************************
'* Name    : SetLocationX                                                   *
'* Purpose : Set cursor x                                                   *
'****************************************************************************
private Inline Sub SetLocationX(x As _posX)
End Sub

'****************************************************************************
'* Name    : SetLocationY                                                   *
'* Purpose : Set cursor y                                                   *
'****************************************************************************
private Inline Sub SetLocationY(y As _posY)
End Sub

'****************************************************************************
'* Name    : WriteAt                                                        *
'* Purpose : Calls one or more WriteItem() subroutines at location x, y     *
'****************************************************************************
Public Compound Sub WriteAt(SetLocationY, SetLocationX, MoveTo, WriteItem) 

'****************************************************************************
'* Name    : Cls                                                            *
'* Purpose : Clear the LCD screen                                           *
'****************************************************************************
Public Sub Clear()
   LcdSendCmd(cmdClear)
End Sub

//-------------------------------------------------------------------------------------------------------------------------------
/*
Timer interrupt for LCD queue
Two distinctive time intervals are used – one for fast commands and data and the second one for slow commands Clear and Home.
When the queue is empty the timer is stopped since there is no reason to run it anymore. When data is placed into the queue the timer is started again.
Commands in the queue are preceded by a special “flag”: the ISR tracks that and sends data to either command or data register.
*/

private interrupt OnTimer3(Pic.T3Interrupt)

    _DSWPAG = CS_BASE                                  //SET EXTENDED DATA SPACE PAGE                        
 
    IFS0.8 = 0                                           //clear T3 interrupt flag

     LcdTx_Tail +=1                                       //Advance read buffer pointer to next byte

   #if LCD_TX_BUFMASK < 255
       LcdTx_Tail = (LcdTx_Tail and LCD_TX_BUFMASK)      //only use 255 byte buffer?   
   #endif
 
   select case state                                    //State machine for command/data interpretation
     
      case 0:                                           //read byte, send data
         if(LcdTx_Buf(LcdTx_Tail) = CMDFLAG) then     //next byte is a command
            TMR3 = (PR3 - 20)                         // Reset TMR3 longer than the execution time of the rest of the ISR
            state = 1                                 //change states
         else
            LCDDAT = swap(LcdTx_Buf(LcdTx_Tail))      //Send data to LCD
            PR3 = BSP_TMR3_PER_SHORT                   //Set for short delay
         end if

      case 1:                                           //send command
            LCDCMD = swap(LcdTx_Buf(LcdTx_Tail))       //send command
           
            if(LcdTx_Buf(LcdTx_Tail) < 4 ) then        //0-3 are slow commands
               PR3 = BSP_TMR3_PER_LONG                  //set for longer delay
            else
               PR3 = BSP_TMR3_PER_SHORT               //else set standard short delay
            end if

            state = 0                                 //Reset state machine
   end select
   
   if(LcdTx_Head = LcdTx_Tail ) then                  //Stop timer if buffer empty
      T3CON.15= 0                                       //stop the timer
   end if
     
end interrupt
//-------------------------------------------------------------------------------------------------------------------------------
// Swap nibbles in a byte..No other efficient way to do it in Firewing
inline function swap(b as WREG0) as WREG0
   ASM                        
   SWAP.B  W0            //swap byte in W0
   END ASM
   swap = WREG0          //return with swapped byte in W0
End function
//-------------------------------------------------------------------------------------------------------------------------------
// Place a byte in the LCD queue.
private sub LcdSendByte(sendbyte as byte)
   dim tmphead as byte = (LcdTx_Head + 1)    // to “see” the tail index of the buffer

   #if LCD_TX_BUFMASK < 255
      tmphead = tmphead and LCD_TX_BUFMASK
   #endif

   while(tmphead = LcdTx_Tail)               // If the buffer is full, wait.. keep buffer large enough
   end while
   
   LcdTx_Buf(tmphead) = sendbyte               // put in buffer
   LcdTx_Head = tmphead
   T3CON.15 = 1                               // start the timer in case it was stopped

end sub

//-------------------------------------------------------------------------------------------------------------------------------
//send character data to the LCD...insert command flag for command
private sub LcdSendCmd(cmd as byte)   
   LcdSendByte(CMDFLAG)                        // insert command flag symbol
   LcdSendByte(cmd)         
end sub

//-------------------------------------------------------------------------------------------------------------------------------
private sub main()
   // Reset LCD buffer head and tail
  LcdTx_Head = 0   
  LcdTx_Tail = 0

  // Setup Timer 3 for LCD.  The timer will be turned on when the first byte is placed into the queue.
  // TMR3 is set one cycle less than PR3 so the interrupt will happen almost immediately.
  T3CON  = &H0000                  // Use Internal Osc (Fcy), 16 bit mode, no prescaler
  PR3    = BSP_TMR3_PER_SHORT      // set the period
  TMR3   = PR3 - 1                 // one count before interrupt 62.5ns
  IPC2 = 1                        // set Timer 3 interrupt priority
  IFS0.8  = 0                    // clear the interrupt for Timer 3
  enable(OnTimer3)               // enable interrupt for Timer 3
 
  // Set PMP Registers
  _PMCON2 = &h0000                // PMCON2 uses default settings

  _PMCON1 = &H0300                  // address is not multiplexed, master mode, PMCS1 pin used for chip select 1, PMCS2 pin used for chip select 2,
                                   // "smart" address strobes are not used, bus keeper is not used, interrupt at the end of of rd/wr cycle
 
  _PMCON3 = &HC000                  // enable read and write(rd/WR) strobe ports, set address latch pulses width to 1/2 Tcy and hold time to 1/4 Tcy 
   
  _PMCON4 = &H0001                // PMA0 address line is enabled (LCD RS)
 
  _PMCS1CF = &H4720               // enable CS function (activate even though we don’t need CS to drive an LCD.), CS1 hactive high but pin disabled,
                                  // byte enable polarity,write strobe polarity - enable active high, read strobe polarity, READ high, WRITE low
                                  // read/write and enable strobes on single pin, data bus width is 8 bit
 
  _PMCS1BS = (CS_BASE>>8)           // CS1 start address
 
  _PMCS1MD = &H00E3               // PMACK is not used,/CS setup time from RS,RW to E (203ns), E strobe length - 450ns by spec  (503 ns), Data hold time from E to valid data 202 ns)
     
  _PMCON1.15 = 1                    // enable the module

#if LCD_INTERFACE = 4 then
   delayms(15)
   
   // Initialize LCD for 4-bit mode
   LCDCMD = swap(&H03)
   delayms(5)
   LCDCMD = swap(&H03)
   delayus(100)
   LCDCMD = swap(&H03)
   delayms(5)
   LCDCMD = swap(&H02)
   delayms(30)
#endif

   // Initialize LCD
   LCDCMD = swap(FUNC_SET)
   delayms(30)
   LCDCMD = swap(DISP_CTRL)
   delayms(30)
   LCDCMD = swap(cmdClear)
   delayms(30)
   LCDCMD = swap(ENTRY_MODE)
   delayms(30)

end sub

End Module
jmusselman64
 
Posts: 24
Joined: Thu Jan 22, 2015 1:01 am

Re: PIC24F EPMP with EDS

Postby Jerry Messina » Thu Dec 14, 2017 11:24 am

Strange. I took that code and modified back to remove the new definitions for the '_PMCxxx' registers, and it compiled fine for me using the following main program (had to add a dummy definition for the missing const LCD_CLEARDISPLAY):
Code: Select all
Device = 24FJ128GA204    'Select processor

imports epmplcd

sub main()
    epmplcd.clear()
end sub


It might depend on which version of XC16 you're using. I'm using v1.11. I notice the processor header file in XC16 V1.33 has the following...
Code: Select all
#define PMCON1 PMCON1
I'm not sure WHAT that does, or how it could possibly change things, but maybe it's causing some issue like there was for the DSWPAG definition.


still confused on how the assignment 'b as WREG0' works..

Normally the syntax for that function might look like:
Code: Select all
function swap(b as byte) as byte

By substituting 'WREG0' for 'byte' you're telling the compiler to use the WREG0 register to pass/return the parameter.
It's sort of like what happens (sometimes) when using the 'register' keyword in C
Jerry Messina
 
Posts: 280
Joined: Thu Feb 14, 2013 10:16 am

Re: PIC24F EPMP with EDS

Postby jmusselman64 » Thu Dec 14, 2017 4:21 pm

I'm stumped.

When creating a Firewing module, do you compile it separately or when it's listed as an Import in another program?
I downloaded XC16 1.11 then modified my tsMicrochip.ini file to reflect XC16 V1.1 and get the same errors.
I'm running Windows10 64 bit, but that shouldn't make any difference.
It's especially bizarre since it compiled successfully when it was a stand along program, but when I try to make it a module it starts griping about registers it had no problem with earlier.
Running out of time here now, but later tonight I may try doing this on my old WinXP laptop.

So close, and yet so far..
jmusselman64
 
Posts: 24
Joined: Thu Jan 22, 2015 1:01 am

Re: PIC24F EPMP with EDS

Postby Jerry Messina » Thu Dec 14, 2017 5:35 pm

You don't compile individual modules, you compile the main program module.

The individual modules specified with an 'imports' statement get included and compiled along with it. It's effectively one big source file.
Jerry Messina
 
Posts: 280
Joined: Thu Feb 14, 2013 10:16 am

Re: PIC24F EPMP with EDS

Postby jmusselman64 » Thu Dec 14, 2017 11:30 pm

well that would explain a lot....
I'll try it the right way when I get home!
jmusselman64
 
Posts: 24
Joined: Thu Jan 22, 2015 1:01 am

Previous

Return to Questions

Who is online

Users browsing this forum: No registered users and 1 guest

cron

x