Parallax Forums
  HomeLog InRegisterCommunity CalendarSearch the ForumHelp
   
Parallax Forums > Public Forums > Propeller Chip > Assembly Code Examples for the Beginner  Forum Quick Jump
 
New Topic Post Reply Printable Version
71 posts in this thread.
Viewing Page :
 1  2  3 
[ << Previous Thread | Next Thread >> ] | Show Newest Post First ]

Javalin
Got a Propeller, need some SPIN?

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Jul 2004
Total Posts : 768
 
   Posted 8/20/2006 3:35 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Morning all,
 
I've just started into Propeller Assembly and I am having some issues with controlling/reading pins.
 
I can set a pin high using
 
But how do I set it LOW?  I would assume from SX assembler you'd need a AND of all the pin bits bar pin10 in this case - i.e %111111111111111011111111111 (etc)  Is there an easier way to do this?
 
or      dira, SPI_ASM_CLK   ' output
or      outa, SPI_ASM_CLK   ' HIGH
....
SPI_ASM_CLK             long |< 10
 
I assume the same applies to DIRA and setting input(0) and output(1) modes
 
Then reading pins - I am using the following code - to read INA state:
 
                       
                        mov     input,  #0                         '  Zero input 
                        or      input,  INA                        '  Place the state of all the pins into input
                        and     input,  SPI_ASM_DI                 '  Place the state of SDA into input 
 
                        test    input,  SPI_ASM_DI       WC        '  If input AND SDA = 0  WZ=1 else = 1  WZ=0
                if_c    shl     spiASMData,#1
                if_c    or      spiASMData,#1
                if_nc   shl     spiASMData,#1
.....
 
SPI_ASM_DI  long |< 11
I get values back from it, but wrong ones.
 
As always grateful for any help!
 
James
 
 
Moderator Edit: By popular request this thread is being made a sticky.

Post Edited By Moderator (Chris Savage (Parallax)) : 8/21/2006 5:19:48 PM GMT

Back to Top
 

parsko
Engineer

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Mar 2006
Total Posts : 475
 
   Posted 8/20/2006 4:37 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
xor outa, SPI_ASM_CLK 'Toggle SPI_ASM_CLK

You were off by one letter!

Read the definition of XOR. Basically, only when both xor'd variable are 0's or 1's, you get a zero. When they are opposite, xor 0 with 1, you get a 1. See page 271 in the manual, then write out (like in 3rd grade arithmatic) the 32 bit "outa" and xor it with 32 bit "SPI_ASM_CLK".

If OUTA was on/high/1
OUTA------------0000_0101_0011_0111_0000_0110_0110_0001
SPI_ASM_CLK--0000_0000_0000_0000_0000_0010_0000_0000
Result-OUTA----0000_0101_0011_0111_0000_0100_0110_0001

If OUTA was off/low/0
OUTA------------0000_0101_0011_0111_0000_0100_0110_0001
SPI_ASM_CLK--0000_0000_0000_0000_0000_0010_0000_0000
Result-OUTA----0000_0101_0011_0111_0000_0110_0110_0001



In assy, it is simply the easiest way to toggle a pin state.

-Parsko

Post Edited (parsko) : 8/20/2006 11:42:23 AM GMT

Back to Top
 

Javalin
Got a Propeller, need some SPIN?

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Jul 2004
Total Posts : 768
 
   Posted 8/20/2006 5:32 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Parsko,

Doesn't that mean that i would need to know the state of the pin before I try to set it high or low?

Im not looking to toggle, but to set the pin state.

Thanks,

James
Back to Top
 

Mike Green
Registered Member



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Oct 2004
Total Posts : 15579
 
   Posted 8/20/2006 6:08 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Use the ANDN instruction with a bit mask to set the pin low (or direction to input) and the OR instruction with the same bit mask to set the pin high (or direction to output). You can use the XOR instruction (with the same bit mask) to toggle the state of the pin.
Back to Top
 

Javalin
Got a Propeller, need some SPIN?

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Jul 2004
Total Posts : 768
 
   Posted 8/20/2006 6:28 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Mike,

Thanks thats what I was after!

James
Back to Top
 

parsko
Engineer

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Mar 2006
Total Posts : 475
 
   Posted 8/20/2006 8:40 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
James,

Sorry, I thought you just wanted to toggle it. When you set a pin to output, doesn't mean it is high, only that it is an output. Mike, I think they should add something that explicit in the manual, you said it well.

-Luke
Back to Top
 

Javalin
Got a Propeller, need some SPIN?

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Jul 2004
Total Posts : 768
 
   Posted 8/20/2006 8:49 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Agreed, I did look but could'nt see anything in the manual for this. The ANDN was too well hidden!

A few more ASM examples in the manual would be helpful!

Thanks for your help!

James
Back to Top
 

Beau Schwabe (Parallax)
IC Layout Engineer



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Aug 2004
Total Posts : 3967
 
   Posted 8/20/2006 9:11 PM (GMT -7)    Quote This PostAlert An Admin About This Post.
My personal opinion on this matter is to use a data MASK for the PIN you want to affect via the Propeller MUX commands.




Example #1 (Set Pin as a OUTPUT - Preset with LOW)

              mov     t1,             #1        wz      '     Configure Pin
              shl     t1,             Pin               '          Create Mask with t1   
              muxz    outa,           t1                '          PreSet DataPin LOW           "0"
              muxnz   dira,           t1                '          Set DataPin to an OUTPUT     "1"





Example #2 (Set Pin as a OUTPUT - Preset with HIGH)

              mov     t1,             #1        wz      '     Configure Pin
              shl     t1,             Pin               '          Create Mask with t1
              muxnz   outa,           t1                '          PreSet DataPin HIGH          "1"
              muxnz   dira,           t1                '          Set DataPin to an OUTPUT     "1"





Example #3 (Set Pin as a INPUT )

              mov     t1,             #1        wz      '     Configure Pin
              shl     t1,             Pin               '          Create Mask with t1
              muxz    dira,           t1                '          Set DataPin to an INPUT      "0"




'Pin' holds a value ranging between 0 and 31 corresponding to the I/O pin you want to affect.


Beau Schwabe

IC Layout Engineer
Parallax, Inc.

Post Edited (Beau Schwabe (Parallax)) : 8/21/2006 4:19:52 AM GMT

Back to Top
 

parsko
Engineer

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Mar 2006
Total Posts : 475
 
   Posted 8/21/2006 12:25 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Beau,

I feel that it is about time that there was an Assembly Tutorial Sticky containing examples just like this, in the same manner that there is a Spin beginners guide. A logical orgainization of it would be nice, but to have one place to look for assy examples would be more nice. I know I could post a few after my "high speed Shiftin" experiences...

-Parsko
Back to Top
 

Beau Schwabe (Parallax)
IC Layout Engineer



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Aug 2004
Total Posts : 3967
 
   Posted 8/21/2006 10:01 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
I agree with the "Assembly Tutorial Sticky" .... The next post is to expand on the earlier examlpes I gave and to start an Assembly Sticky


Beau Schwabe

IC Layout Engineer
Parallax, Inc.

Back to Top
 

Beau Schwabe (Parallax)
IC Layout Engineer



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Aug 2004
Total Posts : 3967
 
   Posted 8/21/2006 10:01 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Using a data MASK for I/O control via the Propeller MUX commands:



{
Example #1 (Set Pin as a OUTPUT - Preset with LOW)
}
PUB start
    cognew(@entry, 0)

DAT

entry         org

              mov     t1,             #1        wz      '     Configure Pin
              shl     t1,             Pin               '          Create Mask with t1  
              muxz    outa,           t1                '          PreSet Pin LOW           "0"
              muxnz   dira,           t1                '          Set Pin to an OUTPUT     "1"
                            
Pin           long      1                               'I/O Pin
t1            res       1                               'Pin Mask






Example #2 (Set Pin as a OUTPUT - Preset with HIGH)
}
PUB start
    cognew(@entry, 0)

DAT

entry         org

              mov     t1,             #1        wz      '     Configure Pin
              shl     t1,             Pin               '          Create Mask with t1
              muxnz   outa,           t1                '          PreSet Pin HIGH          "1"
              muxnz   dira,           t1                '          Set Pin to an OUTPUT     "1"
                            
Pin           long      1                               'I/O Pin
t1            res       1                               'Pin Mask





Example #3 (Set Pin as a INPUT )
}
PUB start
    cognew(@entry, 0)

DAT

entry         org

              mov     t1,             #1        wz      '     Configure Pin
              shl     t1,             Pin               '          Create Mask with t1
              muxz    dira,           t1                '          Set Pin to an INPUT      "0"
                            
Pin           long      1                               'I/O Pin
t1            res       1                               'Pin Mask






'Pin' holds a value ranging between 0 and 31 corresponding to the I/O pin you want to affect.
 
What I have been doing recently is to preserve the "mask" associated to each pin that I am using...
in this case 't1'.  By preserving the "mask" you can use other commands to conviently read or write
to a specific bit.
}






Example #4 (Set P0 as an INPUT and Set P16 an OUTPUT ; Show status of P0 on P16)
}
PUB start
    cognew(@entry, 0)

DAT

entry         org

Initialize
              mov       t1,           #1        wz      '     Configure Output pin
              shl       t1,           #16               '          Create mask with t1      - #P16 LED              
              muxz      outa,         t1                '          Preset Output Pin LOW    "0"
              muxnz     dira,         t1                '          Set pin as Output        "1"

              mov       t2,           #1        wz      '     Configure Input pin
              shl       t2,           #0                '          Create mask with t2      - #P0 I/O             
              muxz      dira,         t2                '          Set pin as Input         "0"
Loop
              test      t2,           ina        wc     '     Read Input pin via 't2' mask
              muxc      outa,         t1                '     Write Output pin via 't1' mask
              jmp       #Loop
                            

t1            res       1                               'Output Mask
t2            res       1                               'Input Mask





{
Example #5 (Simple ShiftIn routine)
}
PUB start
    cognew(@entry, 0)

DAT

entry         org

Initialize
              mov       t1,           #1        wz      '     Configure Output pin
              shl       t1,           #1                '          Create mask with t1      - #P1 Clock               
              muxz      outa,         t1                '          Preset Output Pin LOW    "0"
              muxnz     dira,         t1                '          Set pin as Output        "1"

              mov       t2,           #1        wz      '     Configure Input pin
              shl       t2,           #0                '          Create mask with t2      - #P0 Data              
              muxz      dira,         t2                '          Set pin as Input         "0"

              mov       t3,           #8                '     Set number of bits to 8

Loop          mov       t1,           #0      wz,nr     '     Clock Pin
              muxz      outa,         t1                '          Set ClockPin HIGH
              muxnz     outa,         t1                '          Set ClockPin LOW
             
                                                        '     Shift Bits in 
              test      t2,           ina     wc        '          Read Data Bit into 'C' flag
              rcl       t4,           #1                '          rotate "C" flag into return value

              djnz      t3,           #Loop             '      Decrement t3 ; jump if not Zero
                            

t1            res       1                               'Clock Mask
t2            res       1                               'Data Mask         
t3            res       1                               'Number of Bits
t4            res       1                               'Received Data


Beau Schwabe

IC Layout Engineer
Parallax, Inc.

Post Edited (Beau Schwabe (Parallax)) : 8/21/2006 7:21:59 PM GMT

Back to Top
 

Javalin
Got a Propeller, need some SPIN?

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Jul 2004
Total Posts : 768
 
   Posted 8/21/2006 10:06 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Beau,

Thanks for the post! Very useful!

James
Back to Top
 

parsko
Engineer

Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Mar 2006
Total Posts : 475
 
   Posted 8/21/2006 11:50 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Beau,

Please include the minimum SPIN code. Your example is not so obvious to the beginners that other stuff is necessary. I've got an easy one that might be more ahead of what you suggested to start. It will toggle a pin endlessly...


con
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000
pub Toggle_Main
  cognew (@Toggle, 0)
dat
                        org       0
Toggle
              mov     dira,   Pin                'Set Pin to output
              mov     Time,   cnt                'Place the value of cnt into Time
              add     Time,   #9                 'Add 9 to time
:loop
              waitcnt Time,   Delay              'Set Pin high
              xor     outa,   Pin                'Toggle Pin
              waitcnt Time,   Delay              'Set Pin Low
              xor     outa,   Pin                'Toggle Pin
              jmp     #:loop

Pin     long            |< 1
Delay   long            40_000_000
Time                    res 1


The waitcnt command KILLED me for what seemed to be weeks before the idea went off.

Short of what is happening to waitcnt goes like this, if one were to run through the program line for line:


con
  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000
pub Toggle_Main
  cognew (@Toggle, 0)
dat
                        org       0
Toggle     'This will indicate cnt=0
            mov     dira,   Pin              'cnt=0-3   Set Pin to output
            mov     Time,   cnt            'cnt=4-7   Place the value of cnt into Time
            add     Time,   #9             'cnt=8-11   Add 9 to time
:loop
            waitcnt Time,   Delay       'cnt=12-15   wait until the system counter (cnt) is equal to Time
                                                 'then add the value of Delay to Time or Time=Time+Delay or Time=15+40_000_000
            xor     outa,   Pin             'cnt=40_000_016-40_000_019   Toggle Pin
            waitcnt Time,   Delay       'cnt=40_000_020-40_000_023   wait until cnt equals Time
            xor     outa,   Pin             'cnt=80_000_024-80_000_027 or cnt=24-27 because the counter wraps   Toggle Pin
            jmp     #:loop                 'cnt=28-31   Jump to :loop

Pin     long            |< 1
Delay   long            40_000_000
Time                    res 1


The important note is that you must set the time value of waitcnt before waiting for it. The system will wait until cnt = Time. If your value for Time is less than the system counter at the time the waitcnt command is executed, the COG will pause until CNT wraps around, or about 53 seconds at 80Mhz. So, it sits there waiting, then adds Delay to Time. This is the value that will be used in the NEXT waitcnt command (aka the value used at cnt=40_000_020 above).

Gotta go set the table, more to come...

-Parsko
Back to Top
 

Beau Schwabe (Parallax)
IC Layout Engineer



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Aug 2004
Total Posts : 3967
 
   Posted 8/21/2006 11:59 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Assembly Code Examples for the Beginner: SPI Engine Demo

This Assembly demo illustrates:

1) a Simple SPI Engine (Serial Periphial Interface)
2) Setting up an "Assembly Function" method to be used within Spin
3) A method for passing variables back-n-forth between Spin and Assembly


CON
    _clkmode = xtal1 + pll16x                           
    _xinfreq = 5_000_000

    #0,MSBPRE,LSBPRE,MSBPOST,LSBPOST                                            'Used for SHIFTIN routines
    #4,LSBFIRST,MSBFIRST                                                        'Used for SHIFTOUT routines
    #0,Dpin,Cpin,SLpin,#8,Bits                                                  'Set Dpin,Cpin,SLpin and Bit constant
    
VAR     long          DataValue

OBJ     SPI     : "SPI Engine"

PUB start
{
                        Once called from Spin, SHIFTIN or SHIFTOUT remains running in its own COG.
                        If SHIFTIN or SHIFTOUT are called with 'Bits' set to Zero, then the COG will shut
                        down.  Another way to shut the COG down is to call 'stop' from Spin.
}          
'------------------------------------------------------------------------------------------------------------------------------
    DataValue := %10001011                                                      'DEBUG - Test value

    SPI.SHIFTOUT(Dpin, Cpin, MSBFIRST, Bits, DataValue)                         'Send SHIFTOUT DataVAlue
    
                                                                                'DEBUG - Tested with 74HCT164
                                                                                '        for MSBFIRST and LSBFIRST functions
'------------------------------------------------------------------------------------------------------------------------------
    dira[23..16] := %11111111                                                   'DEBUG - Make ALL LED's outputs

    dira[SLpin] := %1                                                           'Set Shift / Load pin as an output
    repeat
      outa[SLpin] := %1                                                         'Set Shift / Load pin in shift mode "1"
      DataValue := SPI.SHIFTIN(Dpin, Cpin, MSBPRE, Bits)                        'Get SHIFTIN DataValue
      outa[SLpin] := %0                                                         'Set Shift / Load pin in load mode "0"
      
      outa[23..16] := DataValue                                                 'DEBUG - Lightup the LED's corresponding to the data
                                                                                '        LED23 = MSB
                                                                                '        LED16 = LSB
'------------------------------------------------------------------------------------------------------------------------------
{{
                                                                                
Notes:
Consecutive SHIFTIN/SHIFTOUT updates through Spin at 80 MHz is about 14kHz (Bit rate about 112k) 

Consecutive SHIFTIN/SHIFTOUT updates through Assembly at 80 MHz is about 310kHz (Bit rate about 2.5M)

}}




{
                                ********************************************
                                                 SPI Engine             V1.1    
                                ********************************************
                                      coded by Beau Schwabe (Parallax)
                                ********************************************
Revision History:
         V1.0   - original program
         
         V1.1   - fixed problem with SHIFTOUT MSBFIRST option
                - fixed argument allocation in the SPI Engines main loop
}
CON
  #1,_SHIFTOUT,_SHIFTIN
VAR
    long     cog, command, Flag
PUB SHIFTOUT(Dpin, Cpin, Mode, Bits, Value)             'Once called from Spin, SHIFTOUT remains running in its own COG.
    if Flag == 0                                        'If SHIFTOUT is called with 'Bits' set to Zero, then the COG will shut
       start                                            'down.  Another way to shut the COG down is to call 'stop' from Spin.
    setcommand(_SHIFTOUT, @Dpin)
PUB SHIFTIN(Dpin, Cpin, Mode, Bits)|Value               'Once called from Spin, SHIFTIN remains running in its own COG.
    if Flag == 0                                        'If SHIFTIN is called with 'Bits' set to Zero, then the COG will shut
       start                                            'down.  Another way to shut the COG down is to call 'stop' from Spin.
    setcommand(_SHIFTIN, @Dpin)
    result := Value
'------------------------------------------------------------------------------------------------------------------------------
PUB start : okay
'' Start SPI Engine - starts a cog
'' returns false if no cog available
    stop
    Flag := 1
    okay := cog := cognew(@loop, @command) + 1
PUB stop
'' Stop SPI Engine - frees a cog
    Flag := 0
    if cog
       cogstop(cog~ - 1)
    command~
PRI setcommand(cmd, argptr)
    command := cmd << 16 + argptr                       'write command and pointer
    repeat while command                                'wait for command to be cleared, signifying receipt
'################################################################################################################
DAT           org
'  
' SPI Engine - main loop
'
loop          rdlong  t1,par          wz                'wait for command
        if_z  jmp     #loop
              movd    :arg,#arg0                        'get 5 arguments ; arg0 to arg4
              mov     t2,t1                             '    │
              mov     t3,#5                             '───┘ 
:arg          rdlong  arg0,t2
              add     :arg,d0
              add     t2,#4
              djnz    t3,#:arg
              mov     address,t1                        'preserve address location for passing
                                                        'variables back to Spin language.
              wrlong  zero,par                          'zero command to signify command received
              ror     t1,#16+2                          'lookup command address
              add     t1,#jumps
              movs    :table,t1
              rol     t1,#2
              shl     t1,#3
:table        mov     t2,0
              shr     t2,t1
              and     t2,#$FF
              jmp     t2                                'jump to command
jumps         byte    0                                 '0
              byte    SHIFTOUT_                         '1
              byte    SHIFTIN_                          '2
              byte    NotUsed_                          '3
NotUsed_      jmp     #loop
'################################################################################################################
SHIFTOUT_                                               'SHIFTOUT Entry
              mov     t4,             arg3      wz      '     Load number of data bits
    if_z      jmp     #Done                             '     '0' number of Bits = Done
              mov     t1,             #1        wz      '     Configure DataPin
              shl     t1,             arg0
              muxz    outa,           t1                '          PreSet DataPin LOW
              muxnz   dira,           t1                '          Set DataPin to an OUTPUT
              mov     t2,             #1        wz      '     Configure ClockPin
              shl     t2,             arg1
              muxz    outa,           t2                '          PreSet ClockPin LOW
              muxnz   dira,           t2                '          Set ClockPin to an OUTPUT
              sub     LSBFIRST,       arg2    wz,nr     '     Detect LSBFIRST mode for SHIFTOUT
    if_z      jmp     #LSBFIRST_
              sub     MSBFIRST,       arg2    wz,nr     '     Detect MSBFIRST mode for SHIFTOUT
    if_z      jmp     #MSBFIRST_             
              jmp     #loop                             '     Go wait for next command
'------------------------------------------------------------------------------------------------------------------------------
SHIFTIN_                                                'SHIFTIN Entry
              mov     t4,             arg3      wz      '     Load number of data bits
    if_z      jmp     #Done                             '     '0' number of Bits = Done
              mov     t1,             #1        wz      '     Configure DataPin
              shl     t1,             arg0
              muxz    dira,           t1                '          Set DataPin to an INPUT
              mov     t2,             #1        wz      '     Configure ClockPin
              shl     t2,             arg1
              muxz    outa,           t2                '          PreSet ClockPin LOW
              muxnz   dira,           t2                '          Set ClockPin to an OUTPUT
              sub     MSBPRE,         arg2    wz,nr     '     Detect MSBPRE mode for SHIFTIN
    if_z      jmp     #MSBPRE_
              sub     LSBPRE,         arg2    wz,nr     '     Detect LSBPRE mode for SHIFTIN
    if_z      jmp     #LSBPRE_
              sub     MSBPOST,        arg2    wz,nr     '     Detect MSBPOST mode for SHIFTIN
    if_z      jmp     #MSBPOST_
              sub     LSBPOST,        arg2    wz,nr     '     Detect LSBPOST mode for SHIFTIN
    if_z      jmp     #LSBPOST_
              jmp     #loop                             '     Go wait for next command
'------------------------------------------------------------------------------------------------------------------------------              
MSBPRE_                                                 '     Receive Data MSBPRE
MSBPRE_Sin    test    t1,             ina     wc        '          Read Data Bit into 'C' flag
              rcl     t3,             #1                '          rotate "C" flag into return value
              call    #Clock                            '          Send clock pulse
              djnz    t4,             #MSBPRE_Sin       '          Decrement t4 ; jump if not Zero
              jmp     #Update_SHIFTIN                   '     Pass received data to SHIFTIN receive variable
'------------------------------------------------------------------------------------------------------------------------------              
LSBPRE_                                                 '     Receive Data LSBPRE
LSBPRE_Sin    test    t1,             ina       wc      '          Read Data Bit into 'C' flag
              rcr     t3,             #1                '          rotate "C" flag into return value
              call    #Clock                            '          Send clock pulse
              djnz    t4,             #LSBPRE_Sin       '     Decrement t4 ; jump if not Zero
              mov     t4,             #32               '     For LSB shift data right 32 - #Bits when done
              sub     t4,             arg3
              shr     t3,             t4
              jmp     #Update_SHIFTIN                   '     Pass received data to SHIFTIN receive variable
'------------------------------------------------------------------------------------------------------------------------------
MSBPOST_                                                '     Receive Data MSBPOST
MSBPOST_Sin   call    #Clock                            '          Send clock pulse
              test    t1,             ina     wc        '          Read Data Bit into 'C' flag
              rcl     t3,             #1                '          rotate "C" flag into return value
              djnz    t4,             #MSBPOST_Sin      '          Decrement t4 ; jump if not Zero
              jmp     #Update_SHIFTIN                   '     Pass received data to SHIFTIN receive variable
'------------------------------------------------------------------------------------------------------------------------------
LSBPOST_                                                '     Receive Data LSBPOST
LSBPOST_Sin   call    #Clock                            '          Send clock pulse
              test    t1,             ina       wc      '          Read Data Bit into 'C' flag
              rcr     t3,             #1                '          rotate "C" flag into return value
              djnz    t4,             #LSBPOST_Sin      '          Decrement t4 ; jump if not Zero
              mov     t4,             #32               '     For LSB shift data right 32 - #Bits when done
              sub     t4,             arg3
              shr     t3,             t4
              jmp     #Update_SHIFTIN                   '     Pass received data to SHIFTIN receive variable
'------------------------------------------------------------------------------------------------------------------------------
LSBFIRST_                                               '     Send Data LSBFIRST
              mov     t3,             arg4              '          Load t3 with DataValue
LSB_Sout      test    t3,             #1      wc       '          Test LSB of DataValue
              muxc    outa,           t1                '          Set DataBit HIGH or LOW
              shr     t3,             #1                '          Prepare for next DataBit
              call    #Clock                            '          Send clock pulse
              djnz    t4,             #LSB_Sout         '          Decrement t4 ; jump if not Zero
              mov     t3,             #0      wz        '          Force DataBit LOW
              muxnz   outa,           t1
              jmp     #loop                             '     Go wait for next command
'------------------------------------------------------------------------------------------------------------------------------
MSBFIRST_                                               '     Send Data MSBFIRST
              mov     t3,             arg4              '          Load t3 with DataValue
              mov     t5,             #%1               '          Create MSB mask     ;     load t5 with "1"
              shl     t5,             arg3              '          Shift "1" N number of bits to the left.
              shr     t5,             #1                '          Shifting the number of bits left actually puts
                                                        '          us one more place to the left than we want. To
                                                        '          compensate we'll shift one position right.              
MSB_Sout      test    t3,             t5      wc        '          Test MSB of DataValue
              muxc    outa,           t1                '          Set DataBit HIGH or LOW
              shr     t5,             #1                '          Prepare for next DataBit
              call    #Clock                            '          Send clock pulse
              djnz    t4,             #MSB_Sout         '          Decrement t4 ; jump if not Zero
              mov     t3,             #0      wz        '          Force DataBit LOW
              muxnz   outa,           t1
              
              jmp     #loop                             '     Go wait for next command
'------------------------------------------------------------------------------------------------------------------------------
Update_SHIFTIN
              mov     t1,             address           '     Write data back to Arg4
              add     t1,             #16               '          Arg0 = #0 ; Arg1 = #4 ; Arg2 = #8 ; Arg3 = #12 ; Arg4 = #16
              wrlong  t3,             t1
              jmp     #loop                             '     Go wait for next command
'------------------------------------------------------------------------------------------------------------------------------
Clock
              mov     t2,             #0      wz,nr     '     Clock Pin
              muxz    outa,           t2                '          Set ClockPin HIGH
              muxnz   outa,           t2                '          Set ClockPin LOW
Clock_ret     ret                                       '          return
'------------------------------------------------------------------------------------------------------------------------------
Done                                                    '     Shut COG down
              mov     t2,             #0                '          Preset temp variable to Zero
              mov     t1,             par               '          Read the address of the first perimeter
              add     t1,             #4                '          Add offset for the second perimeter ; The 'Flag' variable
              wrlong  t2,             t1                '          Reset the 'Flag' variable to Zero
              CogID   t1                                '          Read CogID
              COGSTOP t1                                '          Stop this Cog!
'------------------------------------------------------------------------------------------------------------------------------
{
########################### Defined data ###########################
}
zero                    long    0                       'constants
d0                      long    $200

MSBPRE                  long    $0                      '          Applies to SHIFTIN
LSBPRE                  long    $1                      '          Applies to SHIFTIN
MSBPOST                 long    $2                      '          Applies to SHIFTIN
LSBPOST                 long    $3                      '          Applies to SHIFTIN
LSBFIRST                long    $4                      '          Applies to SHIFTOUT
MSBFIRST                long    $5                      '          Applies to SHIFTOUT
{
########################### Undefined data ###########################
}
                                                        'temp variables
t1                      res     1                       '     Used for DataPin mask     and     COG shutdown 
t2                      res     1                       '     Used for CLockPin mask    and     COG shutdown
t3                      res     1                       '     Used to hold DataValue SHIFTIN/SHIFTOUT
t4                      res     1                       '     Used to hold # of Bits
t5                      res     1                       '     Used for temporary data mask
address                 res     1                       '     Used to hold return address of first Argument passed

arg0                    res     1                       'arguments passed to/from high-level Spin
arg1                    res     1
arg2                    res     1
arg3                    res     1
arg4                    res     1







Edit: updated SPI engine....
fixed problem with SHIFTOUT MSBFIRST option
fixed argument allocation in the SPI Engines main loop


Beau Schwabe

IC Layout Engineer
Parallax, Inc.

Post Edited (Beau Schwabe (Parallax)) : 8/24/2006 4:40:19 AM GMT



File Attachment :
SPI Demo v1.0 (Aug 23rd 2006).zip   6KB (application/x-zip-compressed)
This file has been downloaded 829 time(s).
Back to Top
 

Phil Pilgrim (PhiPi)
Registered Member



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Feb 2006
Total Posts : 7744
 
   Posted 8/21/2006 1:07 PM (GMT -7)    Quote This PostAlert An Admin About This Post.
Beau (and others),

I think we need to get this copyright thing straightened out. A lot of Propeller code origniating from Parallax (like the SPI Engine above) includes a copyright notice, but no licensing terms. The rules for the Propeller Object Exchange state "no copyrights", which implies public domain (and which is the reason I've never posted anything there). I think a copyright is important, along with a reasonable license for sharing the code. I prefer the General Public License (GPL) for this, and any significant code that I've originated and posted in various forum threads contains those terms of use.

The reasons for having a copyright are several:

1. It allows one to control the attributions that appear in the code, yielding a provenance than can be traced back to the originator.

2. It prevents someone else from copyiing the code and claiming it as their own, as could happen with code in the public domain.

3. It allows one to set restrictions on how the code may be used and distributed, keeping it "free", for example, as the GPL does.

But if copyrighted code is meant to be shared, it needs to say so, even though that intent in the context of the forum might seem clear. Once a program is copied from the forum context, any implied consent disappears. My vote would be to standardize on the GPL for shared Propeller code and to remove the "no copyrights" restriction from the object exchange in favor of that license.

I don't mean to stir up a hornet's nest here, but there are presently some inconsistencies and ambiguities that leave me a little uneasy.

Thanks,
Phil
Back to Top
 

Beau Schwabe (Parallax)
IC Layout Engineer



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Aug 2004
Total Posts : 3967
 
   Posted 8/21/2006 1:21 PM (GMT -7)    Quote This PostAlert An Admin About This Post.
Phil,

You bring up a good point. I will find out what the proper "Header" should look like in released code from Parallax.
What I have been using for the Header of objects that I have been posting originates from objects such as 'Keyboard.spin', 'Mouse.spin', etc....


Beau Schwabe

IC Layout Engineer
Parallax, Inc.

Back to Top
 

Beau Schwabe (Parallax)
IC Layout Engineer



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Aug 2004
Total Posts : 3967
 
   Posted 8/22/2006 12:42 PM (GMT -7)    Quote This PostAlert An Admin About This Post.
It took me a little while to figure this out, so I thought I would pass this along as an Assembler "trap"


Notice, Marker #1 that starts a cognew at TestMode2...

Running this test I would expect LED #16 and LED #17 to turn on.
The actual result is that none of the LED's turn on.


You could change Marker #1 so that it starts a cognew at TestMode1,
and then both LED's would turn on. However for illustration purposes,
TestMode1 happens to do something similar to TestMode2, but it could
be anything.

Remarking the 'call' statement on Marker #4 will cause LED #17 to turn
on as expected and LED #16 will remain off ... as expected. SO why, since
the section between Marker 5&6 is the same as the section between 7&8, won't
this program run the way that it is without remarking the 'call' on Marker #4?

The answer is the way that 'cognew' needs to load your assembly program.
The proper way is to load or start the cog at the top most assembly code
position. In this case the line at Marker #1 should read...


cognew(@TestMode1,0)


...Now at TestMode1 if you want to run or start at TestMode2 then you need a
'jmp' at TestMode1 that points to TestMode2




One more thing to watch out for that has to do with the 'call' function mentioned
in the book is the way that the 'ret' gets encoded into the returning address.
If the lines between Markers 8&9 looked like this...


#8
                 ret
   InitializePins_ret
#9


... Then when 'InitializePins' was actually called, this procedure would not know
where to return. Instead 'ret' MUST be on the same line as the '{Routine}_ret'
in order to properly execute.



Beau Schwabe

IC Layout Engineer
Parallax, Inc.

Post Edited (Beau Schwabe (Parallax)) : 8/22/2006 7:51:54 PM GMT


Image Attachment :
Image Preview
ASMTEST.JPG
  114KB (image/jpeg)
This image has been viewed 12551 time(s).
Back to Top
 

Phil Pilgrim (PhiPi)
Registered Member



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Feb 2006
Total Posts : 7744
 
   Posted 8/22/2006 2:47 PM (GMT -7)    Quote This PostAlert An Admin About This Post.
Beau,

You can also pile up return labels in a subroutine with multiple entry points (or even for multiple subroutines, for that matter, if memory is really tight):


Sub_entry1
               ...Entry1 stuff...
               jmp #Common

Sub_entry2
               ...Entry2 stuff...

Common
               ...Common stuff...

Sub_entry1_ret
Sub_entry2_ret ret


Also, if you need to branch to a subroutine's return point, you don't need to do an immediate jmp. An indirect jmp not only works fine, but saves four clocks:


Sub
           ...
           jmp  Sub_ret
           ...
Sub_ret    ret

The reason this works is that the return address is stored in the src field of the "return" instruction. The indirect jump just picks it up and uses it, instead of going to Sub_ret first.

-Phil
Back to Top
 

Mike Green
Registered Member



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Oct 2004
Total Posts : 15579
 
   Posted 8/22/2006 3:52 PM (GMT -7)    Quote This PostAlert An Admin About This Post.

'' Here's a simple example of table lookup in assembly.
'' This particular example takes a table index in "ptr"
'' and sets the "data" to the word value from the table.
'' There's no range checking.  If you want long values,
'' just eliminate the shifts and masks (*).  You can use
'' the same kind of logic for byte values.

DAT
            org   0
test      mov   ptr,#2        ' for an example, get third value
            call  #look            ' note table indices are 0 to n-1
:stop     jmp   #:stop        ' no checking for out of range

look         mov    data,ptr      ' ptr is a table index (0 to n-1)
              shr   data,#1       ' divide input value by 2 to get (*)
              add    data,#table    '   long word index, add table address
              movs   :inline,data   ' have to use instruction modification
              nop                        ' need pause here for pipelining
:inline    mov    data,0-0         ' get long value from table
              test   ptr,#1   wz     ' do we want odd or even half  (*)
    if_z     and    data,mask       ' if even, take lower 16 bits (*)
    if_nz   shr    data,#16         ' if odd, take upper 16 bits  (*)
look_ret   ret
table       word    $0000
              word    $C0C1
              word    $C181
              word    $0140
              word    $C301
              word    $03C0
              word    $0280
mask        long   $FFFF
data        res     1
ptr          res     1

Post Edited (Mike Green) : 8/23/2006 3:37:17 PM GMT

Back to Top
 

Beau Schwabe (Parallax)
IC Layout Engineer



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Aug 2004
Total Posts : 3967
 
   Posted 8/30/2006 8:35 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
I had mentioned earlier that I preferred using the Propeller MUX operator to affect an I/O pin.
I am still with this belief, however it is application dependent. There are certainly other
times that it is better to use 'or' or 'andn' to affect an I/O bit. The savings can be a few
clock cycles and an extra programming 'long' ...for large applications this can make a big
difference. On smaller applications sometimes you just want to get the job done and it doesn't
really matter one way or the other.

Typically to affect an I/O pin on a 32-bit register you need to create what's known as a 'mask'
To set up a mask simply initialize a mask variable to "1" and then shift that variable left by
the number of bits you wish. For an I/O this translates to the pin you want to access.

Example1:


              mov     PinMask,            #1                '     Set Bit
              shl     PinMask,            IOpin                '     Create PinMask




To define an I/O pin as an input we can use the 'andn' operator AFTER we have created a 'mask'.

Example2:


              andn    dira,            PinMask                '     Make Pin an Input



By using the 'or' operator instead, we can define the I/O pin as an output.

Example3:


              or      dira,            PinMask                '     Make Pin an Output



Using the same 'or' operator and directing it toward 'outa' instead of 'dira' we can define the
pin state as HIGH if we also make the I/O pin an output.

Example4:


              or      outa,            PinMask                '     Make Pin HIGH



Likewise if we use the 'andn' operator we can define the pin state as being LOW if we also make
the I/O pin an output.

Example5:


              andn    outa,            PinMask                '     Make Pin LOW




Suppose we want to affect an I/O pin based on a variable or another I/O pin. One way to do it
using the 'andn' and 'or' operators is to test the variable or pin with a 'mask' and write to
the 'z' flag using the 'if_z' and 'if_nz' conditionals.

Example6:


              mov     InPinMask,        #1                '     Set Bit
              shl     InPinMask,              InPin                       '     Create PinMask
              andn    dira,                     InPinMask                       '     Make Pin an input

              mov     OutPinMask,        #1                '     Set Bit
              shl     OutPinMask,              OutPin                       '     Create PinMask
              or      dira,                     OutPinMask                      '     Make Pin an output


              test    InPinMask,                ina          wz                 '     Test InpinMask against the I/O port
        if_z  andn    outa,                     OutPinMask                      '          If InPin = 0 set OutPin - LOW
        if_nz or      outa,                     OutPinMask                      '          If InPin = 1 set OutPin - HIGH




Another way to accomplish the same task is to use the 'mux' operator.

Example7:


              mov     InPinMask,        #1                '     Set Bit
              shl     InPinMask,              InPin                       '     Create PinMask
              andn    dira,                     InPinMask                       '     Make Pin an input

              mov     OutPinMask,        #1                '     Set Bit
              shl     OutPinMask,              OutPin                       '     Create PinMask
              or      dira,                     OutPinMask                      '     Make Pin an output

              test    InPinMask,                ina          wz                 '     Test InpinMask against the I/O port
              muxnz   outa,                     OutPinMask                      '          If InPin = 0 set OutPin - LOW
                                                                                '          If InPin = 1 set OutPin - HIGH



Now, this may not seem like a huge amount of savings, but this type of scenario has a way of
multiplying itself as a program gets larger, and it can make a difference in the long run with
some applications. Example 8 is a complete Propeller assembly program of Example 7.

Example8: I/O follower


PUB start
    cognew(@entry, 0)

DAT

entry         org

              mov     InPinMask,                #1                              '     Set Bit
              shl     InPinMask,                InPin                           '     Create PinMask
              andn    dira,                     InPinMask                       '     Make Pin an input                     

              mov     OutPinMask,               #1                              '     Set Bit
              shl     OutPinMask,               OutPin                          '     Create PinMask
              or      dira,                     OutPinMask                      '     Make Pin an output

loop          test    InPinMask,                ina     wz                      '     Test Data bit at BitMask position
              muxnz   outa,                     OutPinMask                      '          If InPin = 0 set OutPin - LOW
                                                                                '          If InPin = 1 set OutPin - HIGH
              jmp       #loop 


InPin         long      0                            'I/O pin 0
OutPin        long      16                            'I/o PIN 16 (DEMO Board LED)
 
InPinMask     res       1
OutPinMask    res       1





Example9: MSB - SHIFTIN


PUB start
    cognew(@entry, 0)

DAT

entry         org


Initialize
              mov       ClockPinMask,           #1        wz                    '     Configure Output pin
              shl       ClockPinMask,           ClockPin                        '          Create mask with ClockPin               
              andn      outa,                   ClockPinMask                    '          Preset Output Pin LOW    "0"
              or        dira,                   ClockPinMask                    '          Set pin as Output        "1"

              mov       DataPinMask,            #1        wz                    '     Configure Input pin
              shl       DataPinMask,            DataPin                         '          Create mask with DataPin              
              andn      dira,                   DataPinMask                     '          Set pin as Input         "0"

SHIFTIN       mov       DataBitMask,            #1                              '     Create BitMask
              shl       DataBitMask,            DataBits                        '     Set number of Bits
              mov       Data,                   #0                              '     Clear Data
_ReadNextBit  or        outa,                   ClockPinMask                    '     Set Clock pin HIGH  - Start Clock
              test      DataPinMask,            ina     wc                      '     Load "C" with DataPin value
              muxc      Data,                   DataBitMask                     '     Move "C" into Data via DataBitMask position
              andn      outa,                   ClockPinMask                    '     Set Clock pin LOW   - End Clock
              shr       DataBitMask,            #1      wz                      '     Move BitMask position right by 1           '     
        if_nz jmp       #_ReadNextBit                                           '     Jump to '_ReadNextBit' if there are more bits 


ClockPin      long      0                                                       '     Define I/O Clock Pin
DataPin       long      1                                                       '     Define I/O Data Pin
DataBits      long      7                                                       '     Set number of Data Bits ( N = Bits - 1 = [0 to 31] )  
 
ClockPinMask  res       1                                                       '     ClockPin Mask
DataPinMask   res       1                                                       '     DataPin Mask
DataBitMask   res       1                                                       '     Data Mask
Data          res       1                                                       '     Holds result value from SHIFTIN


Beau Schwabe

IC Layout Engineer
Parallax, Inc.

Back to Top
 

LucidGuppy
Registered Member



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Jan 2005
Total Posts : 32
 
   Posted 11/7/2006 8:25 PM (GMT -7)    Quote This PostAlert An Admin About This Post.
How would I write an assembler routine that would add two numbers and return the result back to the spin object?
Back to Top
 

Beau Schwabe (Parallax)
IC Layout Engineer



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Aug 2004
Total Posts : 3967
 
   Posted 11/7/2006 9:38 PM (GMT -7)    Quote This PostAlert An Admin About This Post.

UPDATE TO THIS POST!!!      04 - 21 - 2007

please see the following thread for an update to this post

http://forums.parallax.com/forums/default.aspx?m=113109&f=25&p=9#m186870

 

 

 

LucidGuppy,

This example will launch a cog just to run the Assembly, then add the two numbers and stop the cog after returning the result.


PUB AddNums(Num1,Num2)
    Num1_ := Num1
    Num2_ := Num2
    cognew(@entry, @Num1)

DAT
                        org
entry                   add     Num1_,Num2_     'Add Num1 to Num2
                        wrlong  Num1_, par      'Return result to Num1
                        CogId   CogNum          'Get COG ID
                        CogStop CogNum          'Stop this COG

CogNum                  long    0               'Reserved variables
Num1_                   long    0
Num2_                   long    0


Beau Schwabe

IC Layout Engineer
Parallax, Inc.

Post Edited (Beau Schwabe (Parallax)) : 4/21/2007 5:07:01 PM GMT



File Attachment :
AddNum_demo.zip   10KB (application/x-zip-compressed)
This file has been downloaded 417 time(s).
Back to Top
 

LucidGuppy
Registered Member



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Jan 2005
Total Posts : 32
 
   Posted 11/8/2006 5:32 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Thanks Beau,

I'll give this a try tonight. Seems like a lot of people are way ahead of me and I need to catch up.
Back to Top
 


Registered Member



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Dec 2005
Total Posts : 154
 
   Posted 12/4/2006 10:38 AM (GMT -7)    Quote This PostAlert An Admin About This Post.

Beau ,

 Where do the results go ? and how can you "look" at them ?

Thanks , Brian

Back to Top
 

Bean
Forum Moderator



Email Address Not AvailablePersonal Homepage Not AvailablePrivate Messaging Not AvailableAIM Not AvailableICQ Not AvailableY! Not AvailableMSN Not Available
Date Joined Jul 2004
Total Posts : 6367
 
   Posted 12/4/2006 10:57 AM (GMT -7)    Quote This PostAlert An Admin About This Post.
Beau,
How long does it take to start/stop a new cog with an assembly program ?
If I have a routine that isn't fast enough in spin, I know it would be faster in assembly, but I don't know what the time delay is to launch a new cog.

Bean.


Cheap used 4-digit LED display with driver IC www.hc4led.com
Low power SD Data Logger www.sddatalogger.com
SX-Video Display Modules www.sxvm.com
 
"People who are willing to trade their freedom for security deserve neither and will lose both." Benjamin Franklin
 

Back to Top
 
[ << Previous Thread | Next Thread >> ]
New Topic Post Reply Printable Version
71 posts in this thread.
Viewing Page :
 1  2  3 
 
Forum Information
Currently it is Thursday, July 29, 2010 5:18 PM (GMT -7)
There are a total of 462,440 posts in 62,066 threads.
In the last 3 days there were 90 new threads and 803 reply posts. View Active Threads
Who's Online
This forum has 20143 registered members. Please welcome our newest member, ME01.
59 Guest(s), 10 Registered Member(s) are currently online.  Details
John Abshier, Rayman, Kevin Wood, BradC, prof_braino, Sapieha, Gene Bonin, laser-vector, localroger, Nick McClick