Free Running Timer in the Spin Interpreter Cog

JonnyMacJonnyMac Posts: 7,098
edited 2020-11-20 - 18:24:42 in Propeller 2
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.
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

  • JonnyMacJonnyMac Posts: 7,098
    edited 2020-11-20 - 23:11:03
    While having lunch and giving my brain a break from a killer P1 project, I thought I'd try the timer in BCD format the way RTC chips operate. It seems to work -- I just don't know if there will be implications for the interpreter. What do you think, @cgracey?

    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 longs
    
  • cgraceycgracey Posts: 13,119
    edited 2020-11-20 - 21:44:00
    If you're asking if this could be added to the interpreter, I would say it's unnecessary, because we have GETSEC() and GETMS() which provide time offsets from boot-up. We'd just need to do a little math, when needed to get the time and data. In either case, there will need to be some initial time-setting. I might be missing the point, though.
  • JonnyMacJonnyMac Posts: 7,098
    edited 2020-11-20 - 21:49:36
    No, that's not what I was asking -- I just wanted to make sure nothing I am doing is unhealthy for the interpreter.

    And, 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.
  • JonnyMac wrote: »
    No, that's not what I was asking -- I just wanted to make sure nothing I am doing is unhealthy for the interpreter.

    And, 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.
    Yes, getms is way more useful. So glad you pushed to get it back in :)
  • Peter JakackiPeter Jakacki Posts: 9,810
    edited 2020-11-21 - 00:26:19
    If you have an I2C RTC you can read that in once at reset or every 24 hours and tie that in with the 64-bit getcnt or getms etc to derive the time of day without wasting time reading the actual hardware each time. As for bcd mode, that's only because RTC chips are still stuck in the 70's/80's and I much prefer a millisecond time of day along with a more conventional calendar date to generate time stamps and report time etc. So I read in my RTC, convert hhmmss to milliseconds and track it from there. When the day has rolled over I resynch with the RTC time and date.
  • I agree with your thoughts, Peter. Again, part of this was a programming exercise to demonstrate a Spin2 feature that is something a little more useful than blinky LEDs.
  • JonnyMac wrote: »
    I agree with your thoughts, Peter. Again, part of this was a programming exercise to demonstrate a Spin2 feature that is something a little more useful than blinky LEDs.

    I agree, I like what you've done.
Sign In or Register to comment.