Free Running Timer in the Spin Interpreter Cog
JonnyMac
Posts: 9,505
The great thing about the Propeller is generally not needing interrupts, but the P2 allows them, and Chip added a neat feature that lets us install interrupt-driven code into the cog that is running the interpreter. This is pretty cool, and that I -- a person who doesn't generally relish coding in PASM -- can pull this off with just a little effort goes to show how nice PASM2 is.
During the Early Adopter meeting Chip explained that hub longs 0..15 are not used and available for our apps. In my case, the long at address 0 holds the timer registers: 1/100ths seconds, seconds, minutes, and hours (bytes 0..3). The long at address 4 is the control value: 0 to reset and stop the timer, 1 to hold the timer at its current value; any other value lets the timer run.
Okay, okay, it's a timer. But the ability to do this within the Spin interpreter cog is making me smile. This is what gets installed in the interpreter cog:
Update: There was a bug in my HOLD state; that's fixed now. I also added a variation that is a running milliseconds timer, like with the Arduino ecosystem. The advantage of this one is that it can be reset, put on hold, and modified.
During the Early Adopter meeting Chip explained that hub longs 0..15 are not used and available for our apps. In my case, the long at address 0 holds the timer registers: 1/100ths seconds, seconds, minutes, and hours (bytes 0..3). The long at address 4 is the control value: 0 to reset and stop the timer, 1 to hold the timer at its current value; any other value lets the timer run.
Okay, okay, it's a timer. But the ability to do this within the Spin interpreter cog is making me smile. This is what gets installed in the interpreter cog:
Update: There was a bug in my HOLD state; that's fixed now. I also added a variation that is a running milliseconds timer, like with the Arduino ecosystem. The advantage of this one is that it can be reset, put on hold, and modified.
dat { timer isr }
timer word start, finish-start-1 ' define chunk start and size-1
org $110 ' org can be $000..$130-size
start mov ijmp1, #isr ' set int1 vector
setint1 #1 ' set int1 to ct-passed-ct1 event
getct pr0 ' get ct
_ret_ addct1 pr0, ##clkfreq_/100 ' set initial ct1 target, return to Spin2
isr rdlong work, #TMR_CTRL wz ' hub $0000_0004 is control
if_z mov tregs, #0 ' reset clock
if_z jmp #update
cmp work, #TMR_HOLD wcz ' hold with current value (fixed)
if_e jmp #exit
' if long[TMR_CTRL] not 0 or 1, let the timer run
adj_timer rdlong tregs, #TMR_REGS ' hub $0000_0000 is timer regs
getbyte work, tregs, #0 ' 1/100ths
incmod work, #99 wc ' increment with rollover
setbyte tregs, work, #0 ' put it back
if_nc jmp #update ' if no rollover, update and exit
getbyte work, tregs, #1 ' seconds
incmod work, #59 wc
setbyte tregs, work, #1
if_nc jmp #update
getbyte work, tregs, #2 ' minutes
incmod work, #59 wc
setbyte tregs, work, #2
if_nc jmp #update
getbyte work, tregs, #3 ' hours
incmod work, #23 wc
setbyte tregs, work, #3
update wrlong tregs, #TMR_REGS ' update the timer
exit addct1 pr0, ##clkfreq_/100
reti1 ' return from interrupt
tregs long 0
work long 0
finish ' 32 longs

Comments
Any guidance on the BCD increment with roll-over? We don't have a digit carry like some processors, so this was the only way I could think of to handle this. Can this be smaller/faster?
dat { timer isr - bcd version } timer word start, finish-start-1 ' define chunk start and size-1 org $100 ' org can be $000..$130-size start mov ijmp1, #isr ' set int1 vector setint1 #1 ' set int1 to ct-passed-ct1 event getct pr0 ' get ct _ret_ addct1 pr0, ##clkfreq_/100 ' set initial ct1 target, return to Spin2 isr rdlong t1, #TMR_CTRL wz ' hub $0000_0004 is control if_z mov tregs, #0 ' reset clock if_z jmp #update cmp t1, #TMR_HOLD wcz ' hold with current value if_e jmp #exit ' if long[TMR_CTRL] not 0 or 1, let the timer run adj_timer rdlong tregs, #TMR_REGS ' hub $0000_0000 is timer regs getbyte t1, tregs, #R_TIX ' 1/100ths mov t2, #$99 ' high value for this bcd byte call #inc_bcd ' increment with rollover setbyte tregs, t1, #R_TIX ' put it back tjnz t1, #update ' done if no rollover getbyte t1, tregs, #R_SECS ' seconds mov t2, #$59 call #inc_bcd setbyte tregs, t1, #R_SECS tjnz t1, #update getbyte t1, tregs, #R_MINS ' minutes mov t2, #$59 call #inc_bcd setbyte tregs, t1, #R_MINS tjnz t1, #update getbyte t1, tregs, #R_HRS ' hours mov t2, #$23 call #inc_bcd setbyte tregs, t1, #R_HRS update wrlong tregs, #TMR_REGS ' update the timer exit addct1 pr0, ##clkfreq_/100 reti1 ' return from interrupt ' increment bcd byte value in t1 ' -- like incmod for bcd byte ' -- limit set by t2 inc_bcd cmp t1, t2 wcz ' at limit? if_e mov t1, #0 if_e ret add t1, #$01 ' increment getnib t2, t1, #0 ' look for $9 -> $A in low nibble cmp t2, #$A wcz if_e add t1, #$06 ' clear low nib, inc high nibble ret tregs long 0 ' timer t1 long 0 ' temp variables t2 long 0 finish ' 46 longsAnd, yes, I pushed for getms() because I will use it. This is a programming exercise, but does have some utility. I have a few P1 programs that run a soft RTC in another cog; I can save the cog using this code in the P2.
I agree, I like what you've done.
This is a nice complement to the Propeller 2 Instructions spreadsheet, which I have been spending some time into. There are certain conventions in the description column of the spreadsheet that I haven't figure out yet but examples like this and the Propeller 1 manual help a lot.