Shop OBEX P1 Docs P2 Docs Learn Events
TACHYON O/S V3.0 JUNO - Furiously Fast Forth, FAT32+LAN+VGA+RS485+OBEX ROMS+FP+LMM+++ - Page 24 — Parallax Forums

TACHYON O/S V3.0 JUNO - Furiously Fast Forth, FAT32+LAN+VGA+RS485+OBEX ROMS+FP+LMM+++

12122242627109

Comments

  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-06-21 19:43
    D.P wrote: »
    I must be missing something with timers
    0 CONSTANT #mt
    : blink #18 PINSET #50 ms  #18 PINCLR  #1000 #mt TIMEOUT ;
    ' blink #mt  ALARM
    
    

    Running blink should blink the light (it does) and reload the timer to blink the light every #1000 ms ?

    I get the first blink and then one blink 1 second later and no more blinks?

    Trying to start the timer with #1000 #mt TIMEOUT does nothing subsequently?

    The timers run in their own cog which uses a WAITCNT to synchronize every 1 ms. By asking that cog to wait for 50ms you are effectively handcuffing it to a chair to do nothing in that period. When it's released WAITCNT now has to wait a complete CNT cycle of 54.6 seconds until it trips again. Don't ever delay a synchronous loop beyond it's window. The other thing is to watch out for shared outputs as running blink from the calling cog will cause the calling cog to assert control over that pin. Fortunately the outputs are or'ed and the calling cog left the pin low so that the timer cog can still override it although the pin is stuck in an output condition (in case you wanted to release it).

    Your code should be a bit like this:
    [FONT=courier new]0 CONSTANT #mt
    : blink #18 PIN@ IF #18 PINCLR #1000 ELSE #18 PINSET #50 THEN [/FONT][FONT=courier new]#mt TIMEOUT [/FONT][FONT=courier new];
    ' blink #mt  ALARM
    
    \ To start the timer from the calling cog it is best just to initiate the timeout (any value)
    1 #mt TIMEOUT
    [/FONT]
    
  • fidolofidolo Posts: 7
    edited 2013-06-22 17:29
    "Sin" is the fetch word. "SIN" takes the actual sine off the stack and prints it out as a decimal.

    One shortcoming of the sine table is that 2049 entries divided by 90 degrees when done by integer math dropping the remainder gets you 22 entries per degree, whereas floating point gets you 22.76666... a not inconsiderable error.

    If anyone can tighten this up ... please do!

    ... enjoy ... BBR

    I ran into the very same question a while back: This is how I resolved it: I decided to treat the sine look-up range as representing 2048 stops per quadrant (from zero for zero to 2047 for almost 90 degrees), even if the intent of the author of the lookup material was to tabulate 2049 stops per quadrant. With 2048 entries of 16-bit each, the really small errors resulting from this possible mistake will not make a noticeable difference because we have only 11 bits of resolution for the angle and 16-bit for its sine.

    Instead, I elected to preserve the main character of the sine function, which is its cyclical nature, all across the board since, mathematically it applies to angles beyond 360 (just rinse and repeat), and it also applies to negative angular values, where the sine of a negative angle is the mirror image (negated value) of the sine of the (positive) mirror angle. So my code is designed to preserve these two features, even at the cost of some really small loss of accuracy.
    : SINE    ( x --- sine) $1FFF AND $1000 U/MOD >L \ wrap at cycle, recognize half cycle 
                            $1000 OVER - MIN         \ reverse indexing for even quadrants
                            2* $E000 + W@            \ read sine in quadrant lookup table
                            L> IF NEGATE THEN ;      \ adjust sign for half cycle
    
    This has worked very well for me, in an application where a 32-bit variable repeatedly accumulates angular increments. It works efficiently because the modulo divisions are replaced with trivial maskings such as
    $1FFF AND
    
    . To achieve truly expeditious execution time, someone with the right skills could write a PASM version of it, and the 2048 vs. 2049 controversy would become lost in the dusty wake of the hyper-propelled roadrunner bird. Mathematicians, please do not take offense, I know you are a whole lot more intellectual than frustrated coyotes!
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-06-22 19:45
    fidolo wrote: »
    I ran into the very same question a while back: This is how I resolved it: I decided to treat the sine look-up range as representing 2048 stops per quadrant (from zero for zero to 2047 for almost 90 degrees), even if the intent of the author of the lookup material was to tabulate 2049 stops per quadrant. With 2048 entries of 16-bit each, the really small errors resulting from this possible mistake will not make a noticeable difference because we have only 11 bits of resolution for the angle and 16-bit for its sine.

    Instead, I elected to preserve the main character of the sine function, which is its cyclical nature, all across the board since, mathematically it applies to angles beyond 360 (just rinse and repeat), and it also applies to negative angular values, where the sine of a negative angle is the mirror image (negated value) of the sine of the (positive) mirror angle. So my code is designed to preserve these two features, even at the cost of some really small loss of accuracy.
    : SINE    ( x --- sine) $1FFF AND $1000 U/MOD >L \ wrap at cycle, recognize half cycle 
                            $1000 OVER - MIN         \ reverse indexing for even quadrants
                            2* $E000 + W@            \ read sine in quadrant lookup table
                            L> IF NEGATE THEN ;      \ adjust sign for half cycle
    
    This has worked very well for me, in an application where a 32-bit variable repeatedly accumulates angular increments. It works efficiently because the modulo divisions are replaced with trivial maskings such as
    $1FFF AND
    
    . To achieve truly expeditious execution time, someone with the right skills could write a PASM version of it, and the 2048 vs. 2049 controversy would become lost in the dusty wake of the propelled roadrunner bird. Mathematicians, please do not take offense, I don't think you are just coyotes!

    Thanks for your input on this matter Fidolo and just to let you know that this whole maths pack thing is being addressed in V3 which uses an auxiliary cog to process maths, dictionary, and timing so therefore the trig functions will be in PASM and I will probably interpolate for finer resolutions.
  • D.PD.P Posts: 790
    edited 2013-06-23 12:58
    The timers run in their own cog which uses a WAITCNT to synchronize every 1 ms. By asking that cog to wait for 50ms you are effectively handcuffing it to a chair to do nothing in that period. When it's released WAITCNT now has to wait a complete CNT cycle of 54.6 seconds until it trips again. Don't ever delay a synchronous loop beyond it's window. The other thing is to watch out for shared outputs as running blink from the calling cog will cause the calling cog to assert control over that pin. Fortunately the outputs are or'ed and the calling cog left the pin low so that the timer cog can still override it although the pin is stuck in an output condition (in case you wanted to release it).

    Thanks for the patience Peter, Tachyon is becoming clearer as I work through examples.

    Here's a little code for anyone who wants to blinks all the lights on the quickstart board using Tachyon timers, thanks again.

    Very impressed with the speed and flexibility of Tachyon. I'm sure the STOPEM word is not optimized but it works for now.
    \ * SIMPLE ROUTINE TO EXERCISE ALL 8 LEDS WITH TACHYON TIMERS
    \ * AND ALARMS ON THE  PROP QUICKSTART BOARD
    \ * 
    \ * USE THE WORD "BLINKEM" TO START THE LIGHTS 
    \ * AND "STOPEM" TO STOP THE LIGHTS
     
    
    0 CONSTANT #mt0
    1 CONSTANT #mt1
    2 CONSTANT #mt2
    3 CONSTANT #mt3
    4 CONSTANT #mt4
    5 CONSTANT #mt5
    6 CONSTANT #mt6
    7 CONSTANT #mt7
    
    : blink0 #16 PIN@ IF #16 PINCLR #500 ELSE #16 PINSET #50 THEN #mt0 TIMEOUT ;
    : b0 ' blink0 #mt0  ALARM ;
    
    : blink1 #17 PIN@ IF #17 PINCLR #300 ELSE #17 PINSET #50 THEN #mt1 TIMEOUT ;
    : b1 ' blink1 #mt1  ALARM ;
    
    : blink2 #18 PIN@ IF #18 PINCLR #150 ELSE #18 PINSET #50 THEN #mt2 TIMEOUT ;
    : b2 ' blink2 #mt2  ALARM ;
    
    : blink3 #19 PIN@ IF #19 PINCLR #285 ELSE #19 PINSET #50 THEN #mt3 TIMEOUT ;
    : b3 ' blink3 #mt3  ALARM ;
    
    : blink4 #20 PIN@ IF #20 PINCLR #175 ELSE #20 PINSET #50 THEN #mt4 TIMEOUT ;
    : b4 ' blink4 #mt4  ALARM ;
    
    : blink5 #21 PIN@ IF #21 PINCLR #335 ELSE #21 PINSET #50 THEN #mt5 TIMEOUT ;
    : b5 ' blink5 #mt5  ALARM ;
    
    : blink6 #22 PIN@ IF #22 PINCLR #100 ELSE #22 PINSET #50 THEN #mt6 TIMEOUT ;
    : b6 ' blink6 #mt6  ALARM ;
    
    : blink7 #23 PIN@ IF #23 PINCLR #195 ELSE #23 PINSET #50 THEN #mt7 TIMEOUT ;
    : b7 ' blink7 #mt7  ALARM ;
    
    : clrpin $FF0000 OUTCLR ;
    
    : BLINKEM  b0 b1 b2 b3 b4 b5 b6 b7  8 0 DO #25 I * 1 + I TIMEOUT LOOP ;
    
    : STOPEM    8 0 DO ' clrpin I ALARM LOOP ;
    
    
  • fidolofidolo Posts: 7
    edited 2013-06-23 20:02
    Thanks for your input on this matter Fidolo and just to let you know that this whole maths pack thing is being addressed in V3 which uses an auxiliary cog to process maths, dictionary, and timing so therefore the trig functions will be in PASM and I will probably interpolate for finer resolutions.

    Thank you Peter, for creating Tachyon Forth in the first place.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-06-25 00:51
    D.P wrote: »
    Thanks for the patience Peter, Tachyon is becoming clearer as I work through examples.

    Here's a little code for anyone who wants to blinks all the lights on the quickstart board using Tachyon timers, thanks again.

    Very impressed with the speed and flexibility of Tachyon. I'm sure the STOPEM word is not optimized but it works for now.
    \ * SIMPLE ROUTINE TO EXERCISE ALL 8 LEDS WITH TACHYON TIMERS
    \ * AND ALARMS ON THE  PROP QUICKSTART BOARD
    \ * 
    \ * USE THE WORD "BLINKEM" TO START THE LIGHTS 
    \ * AND "STOPEM" TO STOP THE LIGHTS
    [/QUOTE]
    
    Thought you'd be interested in this too, it does exactly the same as your blinker except it use a quarter of the code space (95 bytes) and much less dictionary space too (54 bytes). Both yours and this one are just demonstrators so it's good to have a few methods to show what is possible. This one has a common ALARM which reads the index of the caller which in Tachyon is possible since it keeps it's loop parameters on a loop stack vs the traditional return stack. By using a simple table lookup we cut out most of the duplicated code and I've basically left the other words the same. BLINKEM sets the ALARM for each timer to the common method and also starts up the timer with a common value. 
    
    [code][FONT=courier new]
    TABLE timeouts
        #50 || #500 ||
        #50 || #300 ||
        #50 || #150 ||
        #50 || #285 ||
        #50 || #175 ||
        #50 || #335 ||
        #50 || #100 ||
        #50 || #195 ||
    
    : BLINKER ( accesses index of calling loop )
        I 2* 2* timeouts + I 16 + DUP PIN@ IF PINCLR 2+ W@ ELSE PINSET W@ THEN I TIMEOUT
        ;
    
    : clrpin    $FF0000 OUTCLR ;
    
    : BLINKEM    8 0 DO ' BLINKER I ALARM 1 I TIMEOUT LOOP ;
     
    : STOPEM    8 0 DO ' clrpin I ALARM LOOP ;
    [/FONT]
    
  • fidolofidolo Posts: 7
    edited 2013-06-25 13:55
    just to let you know that this whole maths pack thing is being addressed in V3 which uses an auxiliary cog to process maths, dictionary, and timing so therefore the trig functions will be in PASM and I will probably interpolate for finer resolutions.

    Talking about math functions, the unsigned division in Tachyon is best for speed, but when I need signed division, the following redefinition of "/" has worked well for me:
    : /   \ Redefine Division, to allow for algebraic operators ( long long --- long )
                DUP 0< DUP >L  IF NEGATE THEN    \ recognize, save and normalize denominator sign 
          SWAP  DUP 0< DUP >L  IF NEGATE THEN    \ recognize, save and normalize numerator sign
          SWAP  /   L> L> XOR  IF NEGATE THEN ;  \ unsigned divide and apply appropriate sign
    
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-06-26 18:49
    D.P wrote: »
    Thanks for the patience Peter, Tachyon is becoming clearer as I work through examples.

    Here's a little code for anyone who wants to blinks all the lights on the quickstart board using Tachyon timers, thanks again.

    Very impressed with the speed and flexibility of Tachyon. I'm sure the STOPEM word is not optimized but it works for now.

    Hi D.P, I've included a version of your blinkers using TIMERTASK in the Intro page as an example of using TIMEOUTs and ALARMs.

    If anyone would like a few more examples or explanations of some Tachyon functions then I will be happy to include this in the Intro page and for more extensive examples they would have their own project page linked from the Intro/Links section.
  • D.PD.P Posts: 790
    edited 2013-06-26 21:12
    Hi D.P, I've included a version of your blinkers using TIMERTASK in the Intro page as an example of using TIMEOUTs and ALARMs.

    If anyone would like a few more examples or explanations of some Tachyon functions then I will be happy to include this in the Intro page and for more extensive examples they would have their own project page linked from the Intro/Links section.

    Always look forward to the updates. One thing about the last example, when each "alarm" gets set it reads the loop counter I from the loop stack. This "alarm" is then "compiled" and put somewhere with the value of I evaluated and retained. This is fuzzy to me, can you clear it up?
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-06-26 21:49
    D.P wrote: »
    Always look forward to the updates. One thing about the last example, when each "alarm" gets set it reads the loop counter I from the loop stack. This "alarm" is then "compiled" and put somewhere with the value of I evaluated and retained. This is fuzzy to me, can you clear it up?

    The alarm is called from inside a DO LOOP in TIMERTASK and the index of that loop corresponds to the timer. When the alarm function is called there are no parameters passed but the loop index is directly accessible at any time.

    In traditional Forth implementations the loop parameters are stored on the return stack and once you call a word from inside the loop you do not necessarily know depending upon the levels where on the stack that index is. This never made any sense to me and a corruption of the return stack will surely lead to software malfunction, hence the loop stack. Now no matter how much call nesting there is you can count on being able to access the various levels of loop parameters.

    I think by your question though that you may be thinking that the value of the loop index "I" is compiled but it is not. The index "I" is like a variable and when "I" is executed all it does is read this variable (from the loop stack) and place a copy on top of the data stack. Thus each time the alarm word is called it can read which timer activated the call by reading the loop index.

    This is the loop from TIMERTASK and you can see when it executes the CALL that it is related to a particular timer which can be referenced with the loop index "I" as in I TIMER.
    [FONT=courier new][B][COLOR=#000000]    BEGIN                              \ 172us / outer loop measured with a blinky timer[/COLOR] or 118us for 8 empty timers
    [/B][/FONT][B][FONT=courier new][COLOR=#000000]      1 runtime +!                        \ elapsed time since last reset (in ms)[/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]      timercnt C@ 0 [/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]       DO[/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]        I TIMER @ ?DUP                    \ count if it's non-zero[/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]         IF 1- DUP I TIMER !              \ update[/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]           0= IF I TIMER 4 + W@ ?DUP      \ on timeout check for action [/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]             IF CALL THEN THEN            \ execute if set[/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]         THEN[/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]      LOOP[/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]      WAITCNT[/COLOR][/FONT]
    [FONT=courier new][COLOR=#000000]    AGAIN[/COLOR][/FONT]
    [/B]
    
  • max72max72 Posts: 1,155
    edited 2013-06-27 00:30
    [...] If anyone would like a few more examples or explanations of some Tachyon functions [....]
    Intercog communication maybe...
    Cannot get ATN/ENQ usage
  • MJBMJB Posts: 1,235
    edited 2013-06-27 09:51
    max72 wrote: »
    Intercog communication maybe...
    Cannot get ATN/ENQ usage

    it took me a while to understand the use of the I loop index in the example above -
    and when I wanted to post my little EURECA I saw, that Peter was not only faster, but more in depth in his explanation.

    So I will try with my little example for ATN/ENQ - Peter will surely augment it ;-)
    \ Task Example
    LONG tsk1var    \ create a variable, that I will increment inside the new task
                            \ I am thinking about using a COG register for this, but
                            \ the HUB variable is easier to follow
    0 tsk1var !         \ make sure it is 0
    BYTE tsk1COG    here we store the COGid of the COG running our task
    
    : TSK1 ( )         \ a simple task 
                           \ that only adds the command value to its tsk1var variable and returns the value on read
       BEGIN
         ATN@   \ look if there is a new command and place it on the stack, the actual COG knows which mailbox to read from
         ?DUP    \ dup if not 0
         0= NOT  
         IF                           \ so we got a command
            tsk1var +!             \ add command value to HUB variable
            tsk1var @ ENQ!    \ return the new value of our variable as task return message (1 long)
         THEN
       AGAIN                      \ repeat indefinitely,   before here we could place exit code for a given command
       ;
     
    \ run our task 
    ' TSK1                     \ pointer to task word
    TASK?                    \ get a free COG
    DUP tsk1COG C!     \ store it for reference
    RUN                       \ now run it
    
    TASKS                   \ have a look
    tsk1COG C@ .        \ have a look
    
    : TSK1 tsk1COG C@ ;   \ good to have a shortcut
    
    5 TSK1 ATN!                 \  send 5  to our task
    TSK1 ENQ@ .DEC       \  see what the task has done
    
    8 TSK1 ATN!                 \  send 8  to our task
    TSK1 ENQ@ .DEC        \  the task really has done it's job ...
    
    
    Actually I'd love to see a more complex example as well ... ,
    but I have no real application for it right now.

    Markus
  • max72max72 Posts: 1,155
    edited 2013-06-27 13:47
    Thanks Markus,
    I'll study your example.
    The figure of eight code will be reviewed with a multicog code soon..
    Massimo
  • D.PD.P Posts: 790
    edited 2013-06-29 14:02
    The birth of my first Prop I Tachyon FORTH machine.

    It controls a 4th level tool i.e. steam; with files, chisels, stones and such being a 1st level tools.

    Thanks Peter, all

    IMAG0315.jpg
    1024 x 612 - 119K
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-07-01 03:35
    max72 wrote: »
    Intercog communication maybe...
    Cannot get ATN/ENQ usage

    Finally got around to doing this so I incorporated the use of ATN in the timer blinker code. I found a bug in the meantime, well not really a bug but I changed TIMERTASK so that it would check for timing overruns and rather than wait the full 32-bit CNT cycle it now just forces it to the next millisecond. Now this example is only using ATN but ENQ is identical except it's the return channel, a bit like EMIT and KEY where ATN! is equivalent to sending data to that task and ATN@ is equivalent to KEY in that it reads the data. So ENQ! would be used by a task to send a response perhaps and it's similar to EMIT. In this simple example I have the BLINKER task look for commands that in this case we are sending from the console task but could be coming from any task.

    [FONT=courier new]#16    == ledpins
    TABLE blinkrates
        #50 || #500 ||
        #50 || #300 ||
        #50 || #150 ||
        #50 || #285 ||
        #50 || #175 ||
        #50 || #335 ||
        #50 || #100 ||
        #50 || #195 ||
    
    pub BLINKCMD
        ATN@ SWITCH
        "-" CASE blinkrates BL ADO I W@ 2* I W! 2 +LOOP BREAK
        "+" CASE blinkrates BL ADO I W@ 2/ I W! 2 +LOOP BREAK
        ;
    pub BLINKER ( accesses index of calling loop )
        BLINKCMD
        I 2* 2* blinkrates + I ledpins + DUP PIN@ IF PINCLR 2+ ELSE PINSET THEN W@ I TIMEOUT
        ;
    pub clrpin       I ledpins + PINCLR ;
    pub BLINKERS ( flg -- ) IF ' BLINKER ELSE ' clrpin THEN 8 0 DO DUP I ALARM I 1+ I TIMEOUT LOOP DROP ;
    
    ON BLINKERS
    
    { Demonstrates how ATN can be used to send a command to the TIMERTASK
    
    "-" 7 ATN!        \ send a command to TIMERTASK to slow down the blinkers
    
    "+" 7 ATN!        \ speed up the blinkers
    }
    [/FONT]
    
  • max72max72 Posts: 1,155
    edited 2013-07-01 14:09
    Thanks!
    Massimo
  • Brian RileyBrian Riley Posts: 626
    edited 2013-07-03 21:12
    Peter,

    The 'bleeding edge' 2.1 code, 130620, when compiled using SimpleIDE produces the following error
  • D.PD.P Posts: 790
    edited 2013-07-03 21:59
    Looking for a non-blocking SERIN,

    This is my attempt so far, I think this will WAITLOW for 15ms before timing out?
    pub SERINW ( pin -- data )
            
    
            MASK DUP INPUTS DUP
    
            #10000 SWAP WAITLOW  0 > IF     \ wait for low pin 15ms
    
              baudcnt @ 2/ DELTA
    
              baudcnt @ DELTA                        \ delay to sample 1 bit later in 1st data bit
    
                0 8 FOR SHRINP WAITCNT NEXT
    
              NIP #24 SHR                                \ right justify 8-bit data
              
            ELSE
              0
            THEN
            ;
    

    Since the device I'm trying to poll can return 0 as valid data I think I need to set a flag as well. Or DUP the return CNT and check the STACK for a non-zero CNT as the flag.

    Ideas? The device is a touch screen LCD and I need to poll it for async serial messages.

    Writing and receiving is working fine I.E send a message and immediately receive the ACK. Looking for the FORTH POLL, couldn't help that, Peter started it.
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-07-04 01:31
    D.P wrote: »
    Looking for a non-blocking SERIN,

    This is my attempt so far, I think this will WAITLOW for 15ms before timing out?
    pub SERINW ( pin -- data )
            
    
            MASK DUP INPUTS DUP
    
            #10000 SWAP WAITLOW  0 > IF     \ wait for low pin 15ms
    
              baudcnt @ 2/ DELTA
    
              baudcnt @ DELTA                        \ delay to sample 1 bit later in 1st data bit
    
                0 8 FOR SHRINP WAITCNT NEXT
    
              NIP #24 SHR                                \ right justify 8-bit data
              
            ELSE
              0
            THEN
            ;
    

    Since the device I'm trying to poll can return 0 as valid data I think I need to set a flag as well. Or DUP the return CNT and check the STACK for a non-zero CNT as the flag.

    Ideas? The device is a touch screen LCD and I need to poll it for async serial messages.

    Writing and receiving is working fine I.E send a message and immediately receive the ACK. Looking for the FORTH POLL, couldn't help that, Peter started it.
    SERIN amongst a few other functions were introduced as BS2 equivalents and also inheiritx their limitations. V3 will have more in the way of support for PASM objects and you will be able to select multi-port serial objects at runtime. However for now I am not devoting too much effort into doing this with V2 although you could compile the PASM part of a serial object and supply appropriate hooks to it with some Forth words.

    As for a return flag/data sometimes a simple zero is appropriate but not very transparent as zero or null may be part of the data stream. I find returning -1 as data can be interpreted very easily as a negative ack flag. You simply increment the data to test if it is valid data and -1 becomes 0 which means there is no data available.

    As an exercise for V3 I will look at grafting in the multiport serial object into V2 so this should be of some help to you in the meantime as the SERIN object is handy but I consider it a toy and very limited.
  • Brian RileyBrian Riley Posts: 626
    edited 2013-07-04 08:18
    The DropBox Tachyon v21.spin, also tagged 130620, but appears to be a few lines shorter, does compile clean
  • D.PD.P Posts: 790
    edited 2013-07-04 11:00
    SERIN amongst a few other functions were introduced as BS2 equivalents and also inheiritx their limitations. V3 will have more in the way of support for PASM objects and you will be able to select multi-port serial objects at runtime. However for now I am not devoting too much effort into doing this with V2 although you could compile the PASM part of a serial object and supply appropriate hooks to it with some Forth words.

    As for a return flag/data sometimes a simple zero is appropriate but not very transparent as zero or null may be part of the data stream. I find returning -1 as data can be interpreted very easily as a negative ack flag. You simply increment the data to test if it is valid data and -1 becomes 0 which means there is no data available.

    As an exercise for V3 I will look at grafting in the multiport serial object into V2 so this should be of some help to you in the meantime as the SERIN object is handy but I consider it a toy and very limited.

    result DUP 1+ 0 > IF good_data ELSE no_data THEN


    Thanks Peter, that's the good hint I needed. I'm fine with SERIN / OUT now, keep on your V3 path unless you really need the excerise.
  • Brian RileyBrian Riley Posts: 626
    edited 2013-07-04 16:05
    Markus ... I tried this code and couldn't get to work ... what bothered me was you named your shortcut "TSK1" the same as your main task.
    MJB wrote: »
     ...
    
    : TSK1 tsk1COG C@ ;   \ good to have a shortcut
    
    5 TSK1 ATN!                 \  send 5  to our task
    TSK1 ENQ@ .DEC       \  see what the task has done
    
    8 TSK1 ATN!                 \  send 8  to our task
    TSK1 ENQ@ .DEC        \  the task really has done it's job ...
    

    I recoded as such and it worked ... did I miss something and get lucky????
    \ Task Example
    LONG tsk1var	\ create a variable, that I will increment inside the new
                    \   task I am thinking about using a COG register for this,
                    \   but the HUB variable is easier to follow
    0 tsk1var !     \ make sure it is 0
    BYTE tsk1COG    \ here we store the COGid of the COG running our task
    BYTE tsk2COG    \ here we store the COGid of the COG running our task
    BYTE tsk3COG    \ here we store the COGid of the COG running our task
    BYTE tsk4COG    \ here we store the COGid of the COG running our task
    
    : TSK1 ( )      \ a simple task that only adds the command value to its 
    				\    tsk1var variable and returns the value on read
       BEGIN
         ATN@   	\ look if there is a new command and place it on the stack,
     				\   the actual COG knows which mailbox to read from
         ?DUP    	\ dup if not 0
         0= NOT  
         IF         \ so we got a command
            tsk1var +!        \ add command value to HUB variable
            tsk1var @ ENQ!    \ return the new value of our variable as 
    						  \   task return message (1 long)
         THEN
       AGAIN         \ repeat indefinitely, before here we could place exit 
    				 \   code for a given command
       ;
    
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk1COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk2COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk3COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk4COG C!  \ store it for reference
    RUN             \ now run it
     
    
    TASKS                   \ have a look
    tsk1COG C@ .        \ have a look
    
    : TASK1 tsk1COG C@ ;   \ good to have a shortcut
    : TASK2 tsk2COG C@ ;   \ good to have a shortcut
    : TASK3 tsk3COG C@ ;   \ good to have a shortcut
    : TASK4 tsk4COG C@ ;   \ good to have a shortcut
    
    5 TSK1 ATN!                 \  send 5  to our task
    TSK1 ENQ@ .DEC       \  see what the task has done
    
    8 TSK1 ATN!                 \  send 8  to our task
    TSK1 ENQ@ .DEC        \  the task really has done it's job ...
    \ Task Example
    LONG tsk1var	\ create a variable, that I will increment inside the new
                    \   task I am thinking about using a COG register for this,
                    \   but the HUB variable is easier to follow
    0 tsk1var !     \ make sure it is 0
    BYTE tsk1COG    \ here we store the COGid of the COG running our task
    BYTE tsk2COG    \ here we store the COGid of the COG running our task
    BYTE tsk3COG    \ here we store the COGid of the COG running our task
    BYTE tsk4COG    \ here we store the COGid of the COG running our task
    
    : TSK1 ( )      \ a simple task that only adds the command value to its 
    				\    tsk1var variable and returns the value on read
       BEGIN
         ATN@   	\ look if there is a new command and place it on the stack,
     				\   the actual COG knows which mailbox to read from
         ?DUP    	\ dup if not 0
         0= NOT  
         IF         \ so we got a command
            tsk1var +!        \ add command value to HUB variable
            tsk1var @ ENQ!    \ return the new value of our variable as 
    						  \   task return message (1 long)
         THEN
       AGAIN         \ repeat indefinitely, before here we could place exit 
    				 \   code for a given command
       ;
    
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk1COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk2COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk3COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk4COG C!  \ store it for reference
    RUN             \ now run it
     
    
    TASKS                   \ have a look
    tsk1COG C@ .        \ have a look
    
    : TASK1 tsk1COG C@ ;   \ good to have a shortcut
    : TASK2 tsk2COG C@ ;   \ good to have a shortcut
    : TASK3 tsk3COG C@ ;   \ good to have a shortcut
    : TASK4 tsk4COG C@ ;   \ good to have a shortcut
    
    5 TSK1 ATN!                 \  send 5  to our task
    TSK1 ENQ@ .DEC       \  see what the task has done
    
    8 TSK1 ATN!                 \  send 8  to our task
    TSK1 ENQ@ .DEC        \  the task really has done it's job ...
    
    
  • IamretiredIamretired Posts: 56
    edited 2013-07-04 17:56
    I have problem loading EXTEND.fth. After I compile and load Tachyon I get the following screen on Teraterm:
    Propeller .:.:--TACHYON--:.:. Forth V21130224.1600
    Cold start - no user code - setting defaults
    I can execute basic words. Next I try to load EXTEND.fth. The default program on my PC for the suffix fth is Win32Forth. I copy the EXTEND source from Win32Forth IDE and paste it to Teraterm screen. It appears that it is loading, and then it stops on line 40 with:
    0040 hexŠ
  • D.PD.P Posts: 790
    edited 2013-07-04 18:33
    Hey Brian,

    Replace
    5 TSK1 ATN!                 \  send 5  to our task
    TSK1 ENQ@ .DEC       \  see what the task has done
    
    8 TSK1 ATN!                 \  send 8  to our task
    TSK1 ENQ@ .DEC        \  the task really has done it's job ...
    
     \  \ \ \ WITH \ \ \ \ 
    
    5 TASK1 ATN!                 \  ATN!  wants ( cmd cog -- )           in TSK1 -  ATN?  or ATN@ ( -- data ) receive 
    TASK1 ENQ@ .DEC       \  ENQ@ wants ( cog --  data )
    
    8 TASK1 ATN!                 \  send 8  to TASK1
    TASK1 ENQ@ .DEC       \  the task really has done it's job ...  in TSK1 -  ENQ! ( data -- ) response
    
    

    Maybe you had a cut and paste error here.

    BTW with the example code I get 30us round trip so 333K wmsp, (work + message per second), wowza and 6 more cogs just relaxing. I'm gonna needs some NOPs .
    Markus ... I tried this code and couldn't get to work ... what bothered me was you named your shortcut "TSK1" the same as your main task.



    I recoded as such and it worked ... did I miss something and get lucky????
    \ Task Example
    LONG tsk1var	\ create a variable, that I will increment inside the new
                    \   task I am thinking about using a COG register for this,
                    \   but the HUB variable is easier to follow
    0 tsk1var !     \ make sure it is 0
    BYTE tsk1COG    \ here we store the COGid of the COG running our task
    BYTE tsk2COG    \ here we store the COGid of the COG running our task
    BYTE tsk3COG    \ here we store the COGid of the COG running our task
    BYTE tsk4COG    \ here we store the COGid of the COG running our task
    
    : TSK1 ( )      \ a simple task that only adds the command value to its 
    				\    tsk1var variable and returns the value on read
       BEGIN
         ATN@   	\ look if there is a new command and place it on the stack,
     				\   the actual COG knows which mailbox to read from
         ?DUP    	\ dup if not 0
         0= NOT  
         IF         \ so we got a command
            tsk1var +!        \ add command value to HUB variable
            tsk1var @ ENQ!    \ return the new value of our variable as 
    						  \   task return message (1 long)
         THEN
       AGAIN         \ repeat indefinitely, before here we could place exit 
    				 \   code for a given command
       ;
    
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk1COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk2COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk3COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk4COG C!  \ store it for reference
    RUN             \ now run it
     
    
    TASKS                   \ have a look
    tsk1COG C@ .        \ have a look
    
    : TASK1 tsk1COG C@ ;   \ good to have a shortcut
    : TASK2 tsk2COG C@ ;   \ good to have a shortcut
    : TASK3 tsk3COG C@ ;   \ good to have a shortcut
    : TASK4 tsk4COG C@ ;   \ good to have a shortcut
    
    5 TSK1 ATN!                 \  send 5  to our task
    TSK1 ENQ@ .DEC       \  see what the task has done
    
    8 TSK1 ATN!                 \  send 8  to our task
    TSK1 ENQ@ .DEC        \  the task really has done it's job ...
    \ Task Example
    LONG tsk1var	\ create a variable, that I will increment inside the new
                    \   task I am thinking about using a COG register for this,
                    \   but the HUB variable is easier to follow
    0 tsk1var !     \ make sure it is 0
    BYTE tsk1COG    \ here we store the COGid of the COG running our task
    BYTE tsk2COG    \ here we store the COGid of the COG running our task
    BYTE tsk3COG    \ here we store the COGid of the COG running our task
    BYTE tsk4COG    \ here we store the COGid of the COG running our task
    
    : TSK1 ( )      \ a simple task that only adds the command value to its 
    				\    tsk1var variable and returns the value on read
       BEGIN
         ATN@   	\ look if there is a new command and place it on the stack,
     				\   the actual COG knows which mailbox to read from
         ?DUP    	\ dup if not 0
         0= NOT  
         IF         \ so we got a command
            tsk1var +!        \ add command value to HUB variable
            tsk1var @ ENQ!    \ return the new value of our variable as 
    						  \   task return message (1 long)
         THEN
       AGAIN         \ repeat indefinitely, before here we could place exit 
    				 \   code for a given command
       ;
    
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk1COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk2COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk3COG C!  \ store it for reference
    RUN             \ now run it
     
    \ run our task 
    ' TSK1			\ pointer to task word
    TASK?           \ get a free COG
    DUP tsk4COG C!  \ store it for reference
    RUN             \ now run it
     
    
    TASKS                   \ have a look
    tsk1COG C@ .        \ have a look
    
    : TASK1 tsk1COG C@ ;   \ good to have a shortcut
    : TASK2 tsk2COG C@ ;   \ good to have a shortcut
    : TASK3 tsk3COG C@ ;   \ good to have a shortcut
    : TASK4 tsk4COG C@ ;   \ good to have a shortcut
    
    5 TSK1 ATN!                 \  send 5  to our task
    TSK1 ENQ@ .DEC       \  see what the task has done
    
    8 TSK1 ATN!                 \  send 8  to our task
    TSK1 ENQ@ .DEC        \  the task really has done it's job ...
    
    
  • D.PD.P Posts: 790
    edited 2013-07-04 18:55
    Hi LR

    Did you get any errors during the load ? The BELL in the terminal ususally dings and you see many ???. More precisely the module compile result banner displays an error count above 0 and BACKUP does not run, i.e. no swirling bar at the end of the load.

    You may have to play with the line delay and char delay, seems platform, computer dependent IMHO. Here's mine for TeraTerm running in Win7 VM hosted on MAC 10.8.2 You can be sure it's not the delay if you try 3 line and 1 char delay. I have some terminals that need 1 ms per char delay no matter the line delay. If it works, see if you can remove the char delay, this will speed up the load considerably.

    Also where did you get the EXTEND.fth, did you cut and paste from the google docs? If so look for stuff at the bottom from google auto publish and remove it. Same with the TACHYON image although you have the kernel loaded, you are almost home.



    Iamretired wrote: »
    I have problem loading EXTEND.fth. After I compile and load Tachyon I get the following screen on Teraterm:
    Propeller .:.:--TACHYON--:.:. Forth V21130224.1600
    Cold start - no user code - setting defaults
    I can execute basic words. Next I try to load EXTEND.fth. The default program on my PC for the suffix fth is Win32Forth. I copy the EXTEND source from Win32Forth IDE and paste it to Teraterm screen. It appears that it is loading, and then it stops on line 40 with:
    0040 hexŠêüLONG hexptr ???
    pub IDUMP .®¤,ÿ ???
    I waited for 30 minutes without change.

    What am I missing?
    John
  • MJBMJB Posts: 1,235
    edited 2013-07-05 04:40
    @ Brian, D.P

    well Brian, you are right of course, using the same name for the shortcut and the task word is not a good idea.

    nevertheless it worked fine here - which reveals one of the Tachyon internals.
    \ here I define the task word !!!

    : TSK1 ( ) \ a simple task
    \ that only adds the command value to its tsk1var variable and returns the value on read
    BEGIN
    ATN@ \ look if there is a new command
    ?DUP \ dup if not 0
    0= NOT
    IF
    tsk1var +! \ add command value to variable
    tsk1var @ ENQ! \ return the new value of our variable as task return message (1 long)
    THEN
    AGAIN
    ;

    \ here the task is started given the reference from the defines TSK1 word - all fine so far

    ' TSK1 \ pointer to task word
    TASK? \ get a free COG
    DUP tsk1COG C! \ store it for reference
    RUN \ now run it

    TASKS \ have a look
    tsk1COG C@ . \ have a look

    \ now here is the tricky part ( which happened by accident - but worked in this special case )
    \ I redefine the TSK1 word as my shortcut.
    \ But the task is still running and redefining the word is no problem --- yet ;-)
    : TSK1 tsk1COG C@ ; \ good to have a shortcut

    \ so this works here
    5 TSK1 ATN! \ send 5 to our task
    TSK1 ENQ@ .DEC \ see what the task has done

    \ BUT restarting the TSK1 again will of course not work any more ...

    \ Brian, you extended my little example to 4 COGS running the same code.
    \ all of them incrementing the same HUB variable
    \ the semantics of this could be quite 'interesting'
    \ after each command a task receives the HUB variable is incremented AND
    \ the CURRENT value of it stored in the tasks ENQ@ place
    \ so imagine what happens if multiple ATN! to different tasks get mixed
    \ with ENQ@ to those tasks ... in out of order sequence ...
    \ well - it just started as a simple example ...

    \ instead of the 4 separate variables I would use a little table


    TABLE mytasks 4 ALLOT

    complete EXTENDED example below:
    \ Task Example V2
    LONG tsk1var \ create a variable, that I will increment inside the new tasks
                 \ I am thinking about using a COG register for this, but
                 \ the HUB variable is easier to follow
    0 tsk1var !  \ make sure it is 0
    
    \ prepare for 4 tasks
    TABLE myTaskCog 4 ALLOT  \ here we store the COGid of the COGs running our task
    
    : TSK1 ( )  \ a simple task 
       \ that only adds the command value to its tsk1var variable and returns the value on read
       BEGIN
      ATN@   \ look if there is a new command
      ?DUP   \ dup if not 0
      0= NOT
      IF 
      tsk1var +!     \ add command value to variable
      tsk1var @ ENQ! \ return the new value of our variable as task return message (1 long)
      THEN
       AGAIN 
       ;
       
    
    ' TSK1    \ put pointer to task word on stack
    4 0 DO    \ start 4 tasks
      DUP       \ so we can reuse task pointer
      TASK?     \ get a free COG
      DUP myTaskCog I +  C!  \ store it for reference in our little table ( I like those tables .. )
      RUN       \ now run it 
    LOOP
    
    
    
    TASKS           \ have a look
    myTaskCog C@ .    \ have a look
    myTaskCog 1 + C@ .    \ have a look
    myTaskCog 2 + C@ .    \ have a look
    myTaskCog 3 + C@ .    \ have a look
    
    \ my new universal shortcut
    : TSK ( tasknumber - taskCogID ) \ tasks counting from 1
       myTaskCog + 1- C@ ;
    
    \ now I can write:
    5 1 TSK ATN!
    \ and
    1 TSK ENQ@ .DEC
    
    \ or I can go next level
    : TSK!  ( tasknumber - )
       TSK ATN! ;
    
    \ and
    : TSK@  ( tasknumber - )
       TSK ENQ@ ;
     
    : .TSK  TSK@ .DEC ;
    
     
    \ gives
    5 1 TSK!   \ to send message to task 1
    \  and
    1 TSK@     \ to ask task 1 for result
    
    
    \ timing
    NEWCNT
    1000 0 DO
        1 TSK!
        1 TSK@
    LOOP
    LAPCNT .DEC   \ 105us/loop
    
    \ clear 
    0 tsk1var !
    tsk1var @ .DEC
    
    \ now let's get fancy ;-)
    10d 1 TSK!
    1 .TSK
    tsk1var @ .DEC
    5 2 TSK!
    tsk1var @ .DEC
    3 3 TSK!
    tsk1var @ .DEC
    3 .TSK
    2 .TSK
    
    
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-07-05 06:32
    The DropBox Tachyon v21.spin, also tagged 130620, but appears to be a few lines shorter, does compile clean

    Sorry Brian, I was in the middle of an edit and I couldn't get back to it for a few days. I was tracking down a bug at the time which I thought was in the kernel but in the process I moved COGID to the cog kernel however the hub version used the addstk method, hence the compile error. Can't tell you much about what I was doing as I've had too much going on and now I forget!
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2013-07-05 06:56
    A couple of quick tips:
    0 tsk1var ! could also be written simply as tsk1var ~
    The tilde operators work just like they do in Spin except in Forth fashion so ~ will accept an address and clear that long. Use the W~ and C~ if you want the word and byte equivalents. Conversely there is the tilde tilde operators to set a variable using ~~

    A table is really long aligned variable that initially does not allocate any storage space. Aligned to a long boundary could waste up to 3 bytes whereas byte variables begin anywhere.
    TABLE myTaskCog 4 ALLOT only creates a single long that is long aligned but you are using byte variables.
    It's just as easy to say LONG myTaskCog but since you don't need this long aligned you can simply define it as a byte variable BYTE myTaskCog 3 ALLOT

    The sequence 0= NOT before an IF is redundant as IF accepts any non-zero value as "true". Also NOT is an alias for 0= anyway. So simply say ?DUP IF.
  • MJBMJB Posts: 1,235
    edited 2013-07-05 08:12
    includes Peters latest remarks
    \ Task Example V3
    LONG tsk1var \ create a variable, that I will increment inside the new tasks
                 \ I am thinking about using a COG register for this, but
                 \ the HUB variable is easier to follow
    tsk1var ~    \  equiv to: 0 tsk1var !  \ make sure it is 0      C~  W~ available as well 
    
    \ prepare for 4 tasks
    BYTE myTaskCog 3 ALLOT  \ since TABLE creates a LONG table we create a BYTE ARRAY this way
                            \ which means simply create a BYTE variable and 3 additional unnamed bytes after it
                            \ here we store the COGid of the COGs running our task
    
    : TSK1 ( )  \ a simple task 
       \ that only adds the command value to its tsk1var variable and returns the value on read
       BEGIN
      ATN@   \ look if there is a new command
      ?DUP   \ dup if not 0
      \ 0= NOT  \ this is redundant !!   0= is the same as NOT and IF takes everything <>0 as TRUE
      IF 
      tsk1var +!     \ add command value to variable
      tsk1var @ ENQ! \ return the new value of our variable as task return message (1 long)
      THEN
       AGAIN 
       ;
       
    
    ' TSK1    \ put pointer to task word on stack
    4 0 DO    \ start 4 tasks
      DUP       \ so we can reuse task pointer
      TASK?     \ get a free COG
      DUP myTaskCog I +  C!  \ store it for reference in our little table ( I like those tables .. )
      RUN       \ now run it 
    LOOP
    
    
    
    TASKS           \ have a look
    myTaskCog C@ .    \ have a look
    myTaskCog 1 + C@ .    \ have a look
    myTaskCog 2 + C@ .    \ have a look
    myTaskCog 3 + C@ .    \ have a look
    
    \ my new universal shortcut
    : TSK ( tasknumber - taskCogID ) \ tasks counting from 1
       myTaskCog + 1- C@ ;
    
    \ now I can write:
    5 1 TSK ATN!
    \ and
    1 TSK ENQ@ .DEC
    
    \ or I can go next level
    : TSK!  ( tasknumber - )
       TSK ATN! ;
    
    \ and
    : TSK@  ( tasknumber - )
       TSK ENQ@ ;
     
    : .TSK  TSK@ .DEC ;
    
     
    \ gives
    5 1 TSK!   \ to send message to task 1
    \  and
    1 TSK@     \ to ask task 1 for result
    
    
    \ timing
    NEWCNT
    1000 0 DO
        1 TSK!
        1 TSK@
    LOOP
    LAPCNT .DEC   \ 105us/loop
    
    \ clear 
    0 tsk1var !
    tsk1var @ .DEC
    
    \ now let's get fancy ;-)
    10d 1 TSK!
    1 .TSK
    tsk1var @ .DEC
    5 2 TSK!
    tsk1var @ .DEC
    3 3 TSK!
    tsk1var @ .DEC
    3 .TSK
    2 .TSK
    
    
  • MJBMJB Posts: 1,235
    edited 2013-07-05 08:24
    In trying to learn TACHYON I am collecting snippets from Peters code and examples.
    Here my notes on creating Arrays or Tables in TACHYON
    \ creating Arrays or Tables in TACHYON
    
    \ create a non/byte aligned byte array of size 4 bytes
    \ since BYTE allocates already one BYTE we only need to ALLOT 3 additional bytes
    BYTE byteArray 3 ALLOT 
    
    \ create a word aligned word array of size 6 words
    \ since WORD allocates already one WORD we only need to ALLOT 10 additional bytes
    WORD wordArray 10 ALLOT
    
    \ create a long aligned TABLE / Array of size 7 longs 
    \ since TABLE does NOT allocate space we do this after
    \ for 7 longs we need to allocate 28 bytes
    TABLE longArray 28 ALLOT 
    
    
    \ Accessing of BYTE-Arrays just by C! and C@ (with 0 based index)
    value byteArray index + C!       \ store to array
    byteArray index + C@             \ fetch from array
    
    \ Accessing of WORD-Arrays just by W! and W@ (with 0 based index)
    value wordArray index 2* + W!    \ store to array
    wordArray index 2* + W@          \ fetch from array
    
    \ Accessing of LONG-Arrays / TABLES just by ! and @ (with 0 based index)
    value longArray index 2* 2* + !  \ store to array
    longArray index 2* 2* + @        \ fetch from array
    
    
Sign In or Register to comment.