a question about waitcnt in PASM

msrobotsmsrobots Posts: 1,642
edited July 15 in Propeller 1 Vote Up0Vote Down
I want to synchronize a loop to some frequency say 1 Hz
time long 0
delay long 0

                        rdlong     delay,                    #0                     'wait a 1 Hz (clockfreq)


startclockloop          mov     time,                   CNT
                        add     time,                   #32                     'initial delay for first clock as short as good
clockloop
                        waitcnt time,                   delay                   'this should get my clock time synced
...
other stuff happens here
...
                        jmp     #clockloop                                      'if one axis not ready with MOVEMENT next clock


Sadly other stuff happens here COULD exceed the time set for delay, SOMETIMES. By now it just waits 50+ seconds because cnt is already past time when waitcnt gets called.

My goal now is to avoid this over run and instead of waiting 50+ seconds wait as less as possible, best would be no wait. Because I already overrun my time and need to catch up to get synced again.

I am thinking about this
time long 0
delay long 0

                        rdlong     delay,                    #0                     'wait a 1 Hz (clockfreq)


startclockloop          mov     time,                   CNT
                        add     time,                   #32                     'initial delay for first clock as short as good
clockloop

                        cmp     time,                   CNT wz, wc                                           
              if_be   mov     time,                   CNT
              if_be   add     time,                   #9                       'or even shorter

                        waitcnt time,                   delay                   'this should get my clock time synced, now I avoid the 50+ delay but am permanent off original sync
...
other stuff happens here
...
                        jmp     #clockloop                                      'if one axis not ready with MOVEMENT next clock


First I am not sure if I need to use CMP or CMPS to compare time with CNT.

Second I now have modified time, so this delay will persist and I am off my frequency and can never catch up because the original time sync is gone.

Another thought is this
time long 0
delay long 0

                        rdlong     delay,                    #0                     'wait a 1 Hz (clockfreq)


startclockloop          mov     time,                   CNT
                        add     time,                   #32                     'initial delay for first clock as short as good
clockloop

                        cmp     time,                   CNT wz, wc                                           
              if_be   add     time,                   delay
              if_a      waitcnt time,                   delay                   'this should get my clock time synced, now I avoid the 50+ delay time is still in sync?
...
other stuff happens here
...
                        jmp     #clockloop                                      'if one axis not ready with MOVEMENT next clock


Any help welcome here, there should be a known practice to shorten/avoid the 50+ second problem

Enjoy!

Mike
I am just another Code Monkey.

A determined coder can write COBOL programs in any language. -- Author unknown.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.

Comments

  • 22 Comments sorted by Date Added Votes
  • If you have them to spare, you could use a counter to clock a pin at the 1Hz rate, and use a waitpeq.
    It waits if the loop finishes early, breezes on through if it finishes late, and remains synchronized.
  • T ChapT Chap Posts: 3,607
    edited July 16 Vote Up0Vote Down
    I agree that using a pin has several advantages Especially when it comes to ramping up and ramping down the click. There are more things that will come up where you need to adjust the clock One example is when the distance is not great enough for the motor to accelerate to the full speed requested for the move. Some logic will be needed to manage speeds in such cases. Another case is a user initiated pause where a graceful decel is needed from the moment of the pause. Then there could be a resume. An e-stop would be a hard stop of all motors but at a certain running speed you cannot hard stop a stepper without losing position. A pause would not be controlled under gcode but rather with machine methods as described. So as Chris said but I suggest not a counter as a counter cannot manage acceleration and decel on its own but rather a separate cog that manages a clock that contains its own methods to accel and decel. Furthermore this clock engine may need to drive the pin via a counter to be able to define a pulse length.
  • I setup my synchronized loops as you suggest in your second section: 1) synchronize the timer (adding cnt register) just before entering the loop, 2) put you waitcnt at the end.
    dat
    
                            org     0
    
    one_sec                 rdlong  looptix, #0                     ' ticks in 1s
                            mov     pulsetix, looptix
                            shr     pulsetix, #3                    ' pulse is 1/8s
    
                            mov     mask, #1
                            shl     mask, #26                       ' led on pab
                            mov     dira, mask                      ' set to output
    
                            mov     looptimer, looptix
                            add     looptimer, cnt                  ' sync with system timer
    
    loop_code               mov     pulsetimer, pulsetix            ' heartbeat pulse
                            add     pulsetimer, cnt     
                            or      outa, mask
                            waitcnt pulsetimer, #0
                            andn    outa, mask
    
                            { other loop code }
    
                            waitcnt looptimer, looptix
                            jmp     #loop_code 
    
    
    looptix                 res     1
    pulsetix                res     1
    mask                    res     1
    looptimer               res     1
    pulsetimer              res     1
    
    Jon McPhalen
      *It's "Jon" or "JonnyMac" -- please don't call me "Jonny"
  • jmgjmg Posts: 10,345
    msrobots wrote: »
    I want to synchronize a loop to some frequency say 1 Hz
    ...
    Sadly other stuff happens here COULD exceed the time set for delay, SOMETIMES. By now it just waits 50+ seconds because cnt is already past time when waitcnt gets called.

    My goal now is to avoid this over run and instead of waiting 50+ seconds wait as less as possible, best would be no wait. Because I already overrun my time and need to catch up to get synced again.
    ...
    Second I now have modified time, so this delay will persist and I am off my frequency and can never catch up because the original time sync is gone.

    Another thought is this (rounds up)
    ...
    Any time you over-run your base timer, something has to give.
    Your system design dictates what.

    One important question, is how often does the over-run occur ?
    If it is rare, and never more than a 'next tick' in stretch, you have simpler choices.

    You can round-up to the next time quanta, in which case you stay in phase, but have skipped a whole time slot.
    This would be a good choice in a design where staying in phase matters most.

    or, you can just 'get to it as soon as you can', which gives some jitter but is closer to correct time.

    You can use WAITCNT for lowest jitter, as that resolves to SysCLK, or you could [read and compare >=] which solves the 50+ jump, but does have a more coarse exit jitter.
    (of course, once you have over-run, exit jitter of a few cycles is a moot point )
  • msrobotsmsrobots Posts: 1,642
    edited July 16 Vote Up0Vote Down
    OK,

    thank you Jon. So that part I got right since you are doing it also...

    as of putting the waitcnt at the end of the loop, its like repeat until or repeat while or - say a style issue. And usually I like the way you program, very readable. So I try to learn from you.

    But it should not make any difference on re-syncing.

    T Chap and ChrisGadd sadly completely miss the point.

    I use my stepper code as example, but the question is not about the stepper code and how to improve it with pins instead of longs, that belongs into the other thread and is a perfect valid question.

    But here I am looking for a solution to re-sync a waitcnt loop thru catching up once it got out of sync.

    I try my question again, with different wording.

    My timed loop works fine, - and since Jon confirmed it, nothing is wrong with it.

    PASM waitcnt time, delay - waits for cnt to hit the same value as time has, then adds delay to time (preparing the new wait value) and continuous.

    Works fine unless your code in between that loop does exceed delay clock ticks. Then time is less as CNT and CNT needs to go around to catch it. There we have our 50+seconds delay.

    what I was thinking now is instead of calling waitcnt time,delay (since I am late anyways) to just add delay to time and skip waitcnt.

    IF my time value is less then CNT(+x?) skip waitcnt, just add delay to time else do the waitcnt

    And if the next loops I go thru do not exceed their time limit, I am able to catch up to the original timing.

    So my question still is how to find out if time is less then CNT? Even better if time is less then CNT - #16 or so to compensate for the instructions in between.

    I rather wait 16 clocks as 50+seconds

    so how to compare CNT with a long and to decide if the long is BIGGER then CNT so no overflow happens and I can call waitcnt else skip it.

    I am simply unsure how waitcnt handles overflow conditions, my understanding is that CNT is unsigned.

    IF timer>CNT works as long as I do not hit timer=$FFFA and CNT=1 (or alike) then it goes haywire.
    Because at that time, timer=$FFFA is smaller as CNT=1 , but it isn't.

    help!

    Mike





    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • Take a look at how FullDuplexSerial polls cnt to determine when to send/sample bits.
                            mov     t1,time
                            add     t1,#16            
                            sub     t1,cnt
                            cmps    t1,#0           wc
            if_nc           waitcnt time,delay
            if_c            add     time,delay   
    
  • JonnyMacJonnyMac Posts: 5,739
    edited July 16 Vote Up0Vote Down
    I usually write code like this for Spin routines, but it easily translates to PASM. I hope this is what you're after.
    dat
    
                            org     0
    
    one_sec                 mov     ledpin, par
                            shr     ledpin, #2                      ' fix long alignment
                            mov     ledmask, #1
                            shl     ledmask, ledpin
                            mov     dira, ledmask
    
                            rdlong  looptix, #0                     ' ticks in 1s
                            mov     pulsetix, looptix
                            shr     pulsetix, #3                    ' pulse is 1/8s
    
                            mov     looptimer, looptix
                            add     looptimer, cnt                  ' sync with system timer
    
    loop_start              test    HOLD_MASK, ina          wc      ' look for hold signal
            if_nc           jmp     #heart_beat                     ' no hold, show loop running
                            mov     looptimer, looptix              ' re-start timer
                            add     looptimer, cnt
                            jmp     #loop_start
     
    heart_beat              mov     pulsetimer, pulsetix            ' heartbeat pulse
                            add     pulsetimer, cnt     
                            or      outa, ledmask
                            waitcnt pulsetimer, #0
                            andn    outa, ledmask
    
                            { loop code }
            
                            waitcnt looptimer, looptix
                            jmp     #loop_start
    
    
    HOLD_MASK               long    1 << 15 
    
    ledpin                  res     1
    ledmask                 res     1
    looptix                 res     1
    pulsetix                res     1
    looptimer               res     1
    pulsetimer              res     1
    chkhold                 res     1
    

    As I'm testing this code with a PAB that has LEDs on 26 and 27, I started two cogs with this code like this:
      cognew(@one_sec, 26 << 2)
      cognew(@one_sec, 27 << 2)
    


    Jon McPhalen
      *It's "Jon" or "JonnyMac" -- please don't call me "Jonny"
  • Jon you are tricky.

    Not what I am after, but nice!

    ChrisGadd,

    YES! that seems to look like what I am after. I have to study it for a moment, but thanks...

    Enjoy!

    Mike
    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • @ChrisGadd,

    thank you. This does exactly what I was looking for. Perfect.

    Gosh, I love this forum,

    Enjoy!

    Mike
    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • msrobotsmsrobots Posts: 1,642
    edited July 16 Vote Up0Vote Down
    So the actual solution looks like this
    tmp   long 0
    time  long 0
    delay long 0
    
                            rdlong  delay,                  #0                          'wait a 1 Hz (clockfreq)
    
    startclockloop          mov     time,                   CNT
                            add     time,                   #16                     'initial delay for first clock
    clockloop
    
    ...
    other stuff happens here
    ...
    
                            mov     tmp,                    time                    'this should get my clock time synced
                            add     tmp,                    #16                     'look 16 clocks ahead
                            sub     tmp,                    cnt                     'sub current CNT
                            cmps    tmp,                    #0 wc                   'check overflow
            if_nc           waitcnt time,                   delay                   'do waitcnt
            if_c            add     time,                   delay                   'just add delay
    
                            jmp     #clockloop                                      'if one axis not ready with MOVEMENT next clock
    
    

    and will get a test now in real program ...

    Thanks @ChrisGadd again, and bow deep - to Jon, I put the check down like you preferred...

    Enjoy!

    Mike
    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • pjvpjv Posts: 1,890

    So my question still is how to find out if time is less then CNT? Even better if time is less then CNT - #16 or so to compensate for the instructions in between.

    I rather wait 16 clocks as 50+seconds
    so how to compare CNT with a long and to decide if the long is BIGGER then CNT so no overflow happens and I can call waitcnt else skip it.

    I am simply unsure how waitcnt handles overflow conditions, my understanding is that CNT is unsigned.


    Msrobots,

    I am very familiar with exactly the problem you describe, as that is what I have to deal with in my multi-threaded scheduler.

    You need to do an actual subtract of CNT from TIMER and test the MSB (bit 31) of the result by doing a left shift into CARRY. If the CARRY is set, the result was negative, and if the CARRY is not set, the result was positive. I can think of no other way to do this.

    But do realize that *if* the value of CNT, just happened to be identical to TIMER, and the result then tested as not negative, then you would still be in trouble because by the time you execute the next instruction, the result *will* be negative.

    To solve this, you must first add a small number of bogus counts to TIMER before you do the comparison subtraction. The minimum additional required number needs to allow for all the instruction clocks that will be executed between the subtract instruction and the waitcnt instruction. Don't forget to allow 6 clocks for the waitcnt itself.

    Then, before you execute or skip the waitcnt, subtract the same bogus number from TIMER to keep your sync.

    Sure, there was a tiny hiccup depending on how far the TIMER was *behind* the value in CNT, but sync has not been permanently lost.

    If, however things got so silly that TIMER was 26 seconds (half of 53) different from CNT, then you could never tell who is bigger, and this method cannot work. But that's just for the purists, and this method works just great, millions of times an hour in real life industrial applications.

    I suspect what you are attempting would be a great candidate for my multi threaded cooperative scheduler that lets you run up to 8 independent threads simultaneously in a single COG.

    But that is a whole other discussion.

    Cheers,

    Peter (pjv)
  • OOOH SCH.....

    Jon - I have to redo that. I need the check first, else Hickup will happen.
    tmp   long 0
    time  long 0
    delay long 0
    
                            rdlong  delay,                  #0                          'wait a 1 Hz (clockfreq)
    
    startclockloop          mov     time,                   CNT
                            add     time,                   #16                     'initial delay for first clock
    clockloop
                            mov     tmp,                    time                    'this should get my clock time synced
                            add     tmp,                    #16                     'look 16 clocks ahead
                            sub     tmp,                    cnt                     'sub current CNT
                            cmps    tmp,                    #0 wc                   'check overflow
            if_nc           waitcnt time,                   delay                   'do waitcnt
            if_c            add     time,                   delay                   'just add delay
    
    ...
    other stuff happens here
    ...
    
                            jmp     #clockloop                                      'if one axis not ready with MOVEMENT next clock
    
    

    This way it can't hang...

    Enjoy!

    Mike
    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • I am not sure why I am a so slow learner.

    It bit me again. The 50+ seconds thing.

    And I guess my problem is that I not just need to read the post of @pjv, but READ it again, and try to understand it completely.

    Maybe I am confused here, but I thought
                            mov     tmp,                    time                    'this should get my clock time synced
                            add     tmp,                    #16                     'look 16 clocks ahead WRONG, #18?
                            sub     tmp,                    cnt                     'sub current CNT
                            cmps    tmp,                    #0 wc                   'check overflow
            if_nc           waitcnt time,                   delay                   'do waitcnt
            if_c            add     time,                   delay                   'just add delay
    
    is what it should be, come on, its in FullduplexSerial.

    But then I READ @pjv,s post again and stumbled over "Don't forget to allow 6 clocks for the waitcnt itself"
    Yes who can read has a clear advantage. 6, not 4, so #18 not #16 I said to myself.

    But still no luck. But I am really sure that @pjv knows what he is talking about. I am just to slow to understand it, I said to myself, its your second language, that English, READ it AGAIN.

    "You need to do an actual subtract of CNT from TIMER and test the MSB (bit 31) of the result by doing a left shift into CARRY. If the CARRY is set, the result was negative, and if the CARRY is not set, the result was positive. I can think of no other way to do this."

    Question, is
                            cmps    tmp,                    #0 wc                   'check overflow
            if_nc           waitcnt time,                   delay                   'do waitcnt
            if_c            add     time,                   delay                   'just add delay
    
    the same as
                            shl     tmp,                    #1 wc                   'check overflow
            if_nc           waitcnt time,                   delay                   'do waitcnt
            if_c            add     time,                   delay                   'just add delay
    
    in relation to carry, and if not why not?

    Because somehow, sometimes the 50+ seconds thing still appears...

    Mike




    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • Dave HeinDave Hein Posts: 5,278
    edited July 18 Vote Up0Vote Down
    I think you should subtract 16 instead of adding it. You basically want to compare "time" with CNT+N, where N adjust for the time it takes to do the subtract and compare. Also, you don't need a separate compare. You can just check the carry flag after doing a signed subtract. So the code should look like this.
                            mov     tmp,                    time                    'this should get my clock time synced
                            sub     tmp,                    #16                     'look 16 clocks ahead WRONG, #18?
                            subs    tmp,                    cnt  wc                 'sub current CNT
            if_nc           waitcnt time,                   delay                   'do waitcnt
            if_c            add     time,                   delay                   'just add delay
    
    The offset should be less than 16. Maybe 8 will work, but try 16 first.
  • subtract not add. I need to think.

    Mike


    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • OK,

    I need to know if in Nticks, time is still bigger then CNT.

    That means that time has to be > (CNT (now) + Nticks)

    or shorter time - Nticks has to be > CNT

    subtract, not add. Shoot.

    Thanks Dave, at least it seems to makes sense

    OK I will try

    Mike


    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • @Dave Hein,

    yes, you nailed it. what a stupid thing.

    Thank you a lot.

    Mike
    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
  • pjvpjv Posts: 1,890
    Dave,

    Good catch. In my response to Msrobots I was working from memory of code written quite a long time ago, and my "add" statement was incorrect, it should be "subtract", so please forgive my non-thoroughness.

    For the comparison test to make sense, the TIMER first needs to be reduced by at least the number of ticks that will be consumed by code between the test and the WAITCNT that follows. This is to ensure that by the time the WAITCNT is encountered, TIMER is still (just) larger than CNT, and hence will not "snag". I call this the "snagmargin", and to keep synchronism, it needs to be added back to TIMER.

    Cheers,

    Peter (pjv)
  • Ah nuts, sorry 'bout that. Of course the time register is the target cnt, so subtract to bring the target closer.
    Dave Hein wrote: »
    Also, you don't need a separate compare. You can just check the carry flag after doing a signed subtract.
    Can you verify this? I tried this and it only worked intermittently, and it doesn't seem like it should work.
    In order for this to work, the result of any operation should clear C until cnt (src) has passed the target (dest).
    subs 2,1 wc   ' clears C 
    subs 1,2 wc  ' also clears C
    
    subs $8000_0000, $7FFF_FFFF wc  ' sets C
    subs $7FFF_FFFF, $8000_0000 wc  ' also sets C
    
  • Yes, I believe your right. A SUBS won't handle cases where the counter wraps with respect to the time value. So the CMP is required.
  • pjvpjv Posts: 1,890
    A snippet from my Scheduler:
    AddTime         add     Match,0-0                       'calculate target match time of current thread
                    mov     cnt,Match                       'copy target match into shadow
                    sub     cnt,#SnagMargin                 'allow for snag
                    sub     cnt,cnt                         'test for behind... subtract cnt from shadow
                    shl     cnt,#1                  wc      'put "behind" flag in C
            if_nc   waitcnt Match,#0                        'if not behind then wait for match
                    jmp     CurVect                
    
    Cheers,

    Peter (pjv)
  • I left the cmp in and it seems to work fine now.

    Mike
    I am just another Code Monkey.

    A determined coder can write COBOL programs in any language. -- Author unknown.

    The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this post are to be interpreted as described in RFC 2119.
Sign In or Register to comment.