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]
"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!
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.
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 ;
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.
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]
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
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.
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?
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]
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.
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]
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.
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.
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.
...
: 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 ...
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Š
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 ...
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.
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.
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
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!
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.
\ 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
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
Comments
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:
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. 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 . 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!
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.
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.
Thank you Peter, for creating Tachyon Forth in the first place.
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:
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?
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.
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 ;-)
Actually I'd love to see a more complex example as well ... ,
but I have no real application for it right now.
Markus
I'll study your example.
The figure of eight code will be reviewed with a multicog code soon..
Massimo
It controls a 4th level tool i.e. steam; with files, chisels, stones and such being a 1st level tools.
Thanks Peter, all
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.
Massimo
The 'bleeding edge' 2.1 code, 130620, when compiled using SimpleIDE produces the following error
This is my attempt so far, I think this will WAITLOW for 15ms before timing out?
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.
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.
I recoded as such and it worked ... did I miss something and get lucky????
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Š
Replace
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 .
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.
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.
\ 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 ;-)
complete EXTENDED example below:
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!
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.
Here my notes on creating Arrays or Tables in TACHYON