Shop OBEX P1 Docs P2 Docs Learn Events
Request for help with a linear sensor driver. Assembly code challenge? — Parallax Forums

Request for help with a linear sensor driver. Assembly code challenge?

I'm thinking this may be easy for folks who know their way around Assembly and Spin; I'm a novice at Spin and have no clue with anything Assembly. Would the experts out there kindly take a whack at this?

Problem: If no magnet is present on the sensor, the Propeller "hangs up" when it tries to use the sensor. how could this driver code be changed such that the Prop doesn't wait forever for a return pulse?

Physical device description: Magnetostrictive sensor, specifically the EP2 start/stop model from MTS Sensors, found here: http://www.mtssensors.com/fileadmin/media/pdfs/Products/Industrial/E_Series/Data_Sheet_E-Series_EP2_Start_Stop_551335_EN.pdf

Driver code (not written by me):

'
'  propeller code to return magnet position as a function of
'  MCU clock cycles for the Temposonics EP2D linear position
'  sensor.
'
'
'  LIMITATIONS:
'
'  This function returns the position of the first magnet only.
'  (Magnet nearest head of sensor)
'
'  This function assumes the reference magnet is affixed to the
'  end of the sensor.  It will time out (and fail to loop) if
'  there is no magnet on the sensor. THE CODE SHOULD BE CHANGED
'  TO WAIT A FIXED AMOUNT OF TIME THEN LOOP IF NO VALUE IS
'  RETURNED.  In practice this has not been a problem except when
'  running on a breadboard.
'

'

VAR
  long pulseWidth

PUB start

'' Start a cog with the measurement routine

  return cognew(@entry,@pulseWidth)

  
    
PUB getPosition

'' Return the elapsed time (in clock ticks) between start and stop pulses

   pulseWidth := 0                              ' Use zero to indicate no pulse seen yet
   repeat until pulseWidth                      ' Wait until this changes to non-zero
   return pulseWidth                            ' Return the new value

PUB getPositionNoWait
  return pulseWidth
PUB clearPosition
  pulseWidth := 0

    
DAT
entry   org

init    mov       t1,   #1      wz              '  configure start pin as output
        shl       t1,   startPin
        muxz      outa, t1                      '  set it LOW
        muxnz     dira, t1
        mov       t2,   #1      wz              '  configure stop pin as input
        shl       t2,   stopPin
        muxz      dira, t2
        
        mov       sampTime, cnt                 ' Set up delay
        add       sampTime, #9                  ' Add 9 clk cycles - time needed to execute this block
        waitcnt   sampTime, sampDelay
        
loop    mov       t1,   #0      wz,nr    
        mov       startTime,cnt                 ' Save the system clock value
        muxz      outa, t1                      ' Set the startPin high
        nop
        nop
        nop
        nop
        nop
        nop                                     '  series of NOP's to send 1.5 us pulse
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        muxnz     outa, t1                      ' Set startPin low
        
        nop
        nop
        nop
        nop
        nop
        nop                                     '  series of NOP's to wait for bogus STOP to go low
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop
        nop

        waitpne   zeroes,t2                     ' Wait for stopPin to go high (was #%01)
        mov       endTime,cnt                   ' Get the system clock value
        sub       endTime,startTime             ' Calculate elapsed tim
        wrlong    endTime,PAR                   ' Return the elapsed time
        'mov       tempvar, t2
        'wrlong    tempvar,PAR                   ' Return the elapsed time
        waitcnt   sampTime, sampDelay           ' Pause - sensor sampling freq is ~40Hz
        jmp       #loop                         ' Loop

zeroes        long      0                      
stopPin       long      24                      
startPin      long      25                      
sampDelay     long      2_500_000               ' delay in clock cycles between sensor samples
'trigDelay     long      110
startTime     res       1                       ' Start pulse system clock
endTime       res       1                       ' Stop pulse system clock (and calculated difference)
t1            res       1
t2            res       1
trigTime      res       1
sampTime      res       1
tempvar       res       1




Comments

  • Clock LoopClock Loop Posts: 2,069
    edited 2018-04-06 13:30
    Assuming you are calling start, and then calling getPosition which is waiting forever.

    Your driver is fine(assuming the assembly is fine), it even has a way to do a "no wait get position."

    So from your main code, which isn't attached, don't call "getPosition"



    Call "getPositionNoWait"

    But in a repeat loop. I am guessing if you don't know spin or assembly, then chances are you won't be able to do this even.

    And then you must accept that after you call getPositionNoWait you could have a return value of 0, and deal with it in that loop.

    It looks like your driver is fine, its your top level spin code that calls the driver.

    If you don't know spin, or assembly, you will need someone to help you with the main code, not the driver code you posted here.

    This is one of the problems with many people that need help, they don't, or can't, post ALL the code.
  • Good points Clock Loop - yes, I thought of using getPositionNoWait but thought there might be issues with regard to the rest of the higher-level code. I can do rudimentary things like make loops, but not much else.

    You've corroborated my earlier hunch, thanks - I'm going to take another whack at using the NoWait routine... thanks for taking the time to comment.
  • Clock LoopClock Loop Posts: 2,069
    edited 2018-04-06 14:17
    chris_bfs wrote: »
    I'm going to take another whack at using the NoWait routine... thanks for taking the time to comment.

    As long as the START function is still run, then the background code runs identically whether you run the getposition or getpositionnowait.

    As long as you still run the start function, and even if you never run getposition or getpositionnowait, your sensor is still being measured and stored into @pulseWidth


    If you don't check getpositionnowait fast enough, you might miss something, which is why they probably ran the wait routine.

    Really, unless you are running out of cogs (unlikely), you can just run a loop that repeately checks pulseWidth for a change
    Repeat
        If pulsewidth <> 0
           pulsewidthGood := pulsewidth 
    

    This code will loop forever only storing values into pulsewidthGood that are not 0

    Then pulsewidthGood will always have the last known value that isn't 0.

    This is all very rough code stuff...

  • Thank you Clock Loop!

    I can't report success (or failure) at this point, just wanted to thank you for responding.

    Will report back whether the no-wait routine works out.

    Best,
    Chris
  • jmgjmg Posts: 15,173
    chris_bfs wrote: »
    Problem: If no magnet is present on the sensor, the Propeller "hangs up" when it tries to use the sensor. how could this driver code be changed such that the Prop doesn't wait forever for a return pulse?

    That gets tricky...
    The highest precision comes from using waitpne, as here, but that does mandate a STOP pulse arriving.
    You can do a timeout loop with DJNZ & Pin-poll or similar, but the granularity of that is not as good - perhaps 12x worse.

    What precision can you tolerate ?

    It says the energise pulse is ~1.5us, is the echo pulse the same width ?

    What you might be able to do, is use a Prop CTR in gated mode L=Count, H=Pause
    eg CTRMODE = %10101 increments on !A (Pin A = low)
    You then Clr CTR, then poll pinA, and immediately after poll exit, read the counter.
    The poll loop is coarse, but the counter stops on __/==== with 1 SysCLK precision.
  • waitp(n)e waits for a mask to fill you requirement, not just a single pin. So if you have a spare pin, you can run some time out code in another cog to change that spare pin after x time.

    And on your waitpxx you simply wait for a change of any of them two pins.

    Enjoy!

    Mike
  • msrobots wrote: »
    waitp(n)e waits for a mask to fill you requirement, not just a single pin. So if you have a spare pin, you can run some time out code in another cog to change that spare pin after x time.

    And on your waitpxx you simply wait for a change of any of them two pins.

    Enjoy!

    Mike

    I solved similar situation differently. I needed the precision of waitpne. I used one COG to do this. It would wait indefinitely. Another COG acted as a watchdog and killed it when it timed out.

  • msrobotsmsrobots Posts: 3,709
    edited 2018-04-12 18:51
    differently? That was exactly what I was trying to describe.

    I am still fighting with that English language.

    Ahh, I get it you killed the whole COG?

    Enjoy!

    Mike
  • Just now seeing these, thanks everyone.

    jmg - that's good info; yes, I remember (vaguely), long ago when a developer was taking a look at that code, it seemed we could only get the precision we wanted w/ that assembly routine; but, we may have missed something.

    msrobots and rbehm - that sounds exactly like what I'd like to do.. the accuracy of PNE but with a watchdog or some other method to kill the waiting loop... that's great. Would you please send some example code of how you implemented your particular approach to this? rbehm, I had looked into the watchdog approach before and thought that it wasn't possible, for various reasons. Obviously, if you did it, I was wrong and I'd love to know what I missed.

    Please consider that I'm a novice coder and mostly just try to understand what others have done and modify it; so, I'm hoping any explanations or code samples you post (I hope you do) will be geared for a neophyte but, hey, I'll take whatever you want to send me.

    thanks again, in advance, for any help.
    Chris


    FYI, I'm working on a project which will help ecologists and biologists decrease the costs of taking the data required for their scientific studies. The studies typically relate to the health of aquatic species, but can be amphibians or other species, as well.
  • jmgjmg Posts: 15,173
    chris_bfs wrote: »
    Please consider that I'm a novice coder and mostly just try to understand what others have done and modify it...

    Do you have a spare pin ? The simplest timeout, is as msrobots mentioned above, to use a spare pin, and WAIT masks for either of two pins.
    You might be able to dual-purpose the start pin, if you have nothing to spare.

    A timer can drive that pin, with timeouts up to many seconds.

    How many of these channels do you want to run ?

    What timing precision do you need ? (or, what mm / fraction mm LSB must you get ).



  • jmg, I just re-read your post and that sounds very interesting.

    So, I think I understand what you're saying, but it seems like this approach is just as accurate (or maybe missing a system clock cycle, so .. half the accuracy?) of the other methods. What am I missing, such that this approach is 12x lower resolution? Please expand on the "poll loop" and the inherent "courseness" you mentioned, if you don't mind.

    thanks again - Chris
  • Hi jmg - looks like we may have been posting at about the same time.. my last comment referred to your earlier post, from Apr,11. I'll look into what you posted just now...
  • to answer your questions, jmg, yes I believe we'll have a spare pin open. how would I use such a pin as a participant in the mask such that it could perform the interrupt action for the pnewait?

    thanks much,
    Chris
  • jmgjmg Posts: 15,173
    chris_bfs wrote: »
    to answer your questions, jmg, yes I believe we'll have a spare pin open. how would I use such a pin as a participant in the mask such that it could perform the interrupt action for the pnewait?

    The COG counters can output pulses/frequencies to the pins, one example would be to use this
    data sheet :
    CTRMODE = %00100 NCO single-ended A-Pin = PHSA[31]

    PHSA is then cleared on our start pulse, and set to count at some rate that will set bit31 after some time. (longest possible timeout is 2^31/80M = 26.84s)

    You need to decide what timeout to allow, and configure the FRQA PHSA values.

    WAITPNE then waits on two pins, the Echo pin, and the newly added PHSA[31] pin, and exits pin whichever changes first.

    data sheet:
    WAITPNE D,S Wait for pins not equal (INA & S) != D

    S is the mask, now with 2 bits high, one for Echo Pin, and one for Timer bit31.
    D is idle state, if both pins start 0.0 and you want 1.0 or 0.1 to exit, then D = 00 (exits on either pin high)

    Once it exits, you might want to test which pin causes exit, so timer bit31 gives some clear 'no probe' return value.

  • AribaAriba Posts: 2,690
    With WATPNE you get resolution of 12.5ns, and therefore a sensor value around 2'000'000 at 40 Hz. I don't think such a high resolution makes any sense.

    If you replace the WAITPNE with a loop that checks the pins and a timeout counter, the resolution is reduced to 8*12.5ns = 100ns. This gives values around 250'000 at 40 Hz, still over 18bit resolution.

    So I would replace:
            waitpne   zeroes,t2                     ' Wait for stopPin to go high (was #%01)
    
    with:
            mov       tempvar,timeout
    WtStp   test      t2,ina            wc          ' Wait for stopPin to go high
     if_nc  djnz      tempvar,#WtStp
    
    and add the timeout value to the definitions at the end of the assembly code (before the res variables):
    timeout long      500_000                       ' 50ms (= 20 Hz without sensor)
    
    You can use also shorter or longer timeouts: value = ms * 10_000

    Andy
  • jmgjmg Posts: 15,173
    Ariba wrote: »
    With WATPNE you get resolution of 12.5ns, and therefore a sensor value around 2'000'000 at 40 Hz. I don't think such a high resolution makes any sense.

    You need to check the full-scale delays of the probe, the reading repetition speed does not matter so much.
    From memory, these magnet probes do need quite high clocks to resolve sub-mm LSBs
    The OP has not given a SysCLks per mm for the probes ?
  • @chris_bfs, I agree with Clock Loop, your driver is most likely fine. It is designed to run in the background.
    After browsing the first page of the datasheet it seems somewhat similar to a PING sensor in concept...ultrasonic time of flight.
    I think you could benefit from studying the main 'top' code of the PING sensor.
  • Thank you everyone -

    jmg, it's approx 28.8 clock cycles per mm.

    typical starting clock cycles (at "zero" mm mark) = ~1200

    those are values I'm measuring w/ Propeller and the routine posted, not an external instrument. If the Prop is missing some clock cycles, I wouldn't catch that.

    The label on the side of the sensor says:" GRD: 9.1000uS/ln " The sensors come in both inch and mm varieties and they're the same sensors, just labeled differently for convenience. I have some other sensors, same model but different lengths, some in inch and some in mm. One of the "inch" variety gives a GRD of 9.1847uS/ln. All the GRDs are approx 9.1 but vary by ~.1 either way.

    Thanks Ariba, that's great; I'll give it a try, report back.

    Lardom, I'm sure you're right. I just can't figure out how to interrupt the routine which waits on the pin to toggle (from the top code). I've been working on other things, but will start in on trying to use the no-wait routine. However, when the developer put this together long ago, the "hang up" was a known issue, one we didn't want, so there must be some reason why he didn't use the "no wait" routine (ie, if that one could have worked, seems he would have used it.) I don't know what prevented him, I'm just assuming there's something.

    thanks everyone, for weighing in!


  • Hi Ariba -

    success! The 500_000 value for the timeout was too long, for some reason, but changing it to 200_000 or less and it's working! It may work at larger values, I haven't found the exact "breaking point".

    fantastic, thanks to everyone who replied. I learned a lot, puzzling through all the stuff. Who knows, maybe I'll be able to pick up some assembly.

  • jmgjmg Posts: 15,173
    chris_bfs wrote: »
    jmg, it's approx 28.8 clock cycles per mm.

    typical starting clock cycles (at "zero" mm mark) = ~1200

    those are values I'm measuring w/ Propeller and the routine posted, not an external instrument. If the Prop is missing some clock cycles, I wouldn't catch that.

    The label on the side of the sensor says:" GRD: 9.1000uS/ln " The sensors come in both inch and mm varieties and they're the same sensors, just labeled differently for convenience. I have some other sensors, same model but different lengths, some in inch and some in mm. One of the "inch" variety gives a GRD of 9.1847uS/ln. All the GRDs are approx 9.1 but vary by ~.1 either way.
    Interesting there is a variation, seems the velocity in the material is not exactly the same.

    28.8 SysCLKs / mm is what you get at 80MHz,(12.5ns) so LSB is 0.03472' mm
    The polling loop will be more coarse,(100ns) so some values will skip and LSB is then 0.277' mm

    If that more coarse measurement is ok, the polling code you have working is fine to use.

  • chris_bfs wrote: »
    Just now seeing these, thanks everyone.

    jmg - that's good info; yes, I remember (vaguely), long ago when a developer was taking a look at that code, it seemed we could only get the precision we wanted w/ that assembly routine; but, we may have missed something.

    msrobots and rbehm - that sounds exactly like what I'd like to do.. the accuracy of PNE but with a watchdog or some other method to kill the waiting loop... that's great. Would you please send some example code of how you implemented your particular approach to this? rbehm, I had looked into the watchdog approach before and thought that it wasn't possible, for various reasons. Obviously, if you did it, I was wrong and I'd love to know what I missed.

    Please consider that I'm a novice coder and mostly just try to understand what others have done and modify it; so, I'm hoping any explanations or code samples you post (I hope you do) will be geared for a neophyte but, hey, I'll take whatever you want to send me.

    thanks again, in advance, for any help.
    Chris


    FYI, I'm working on a project which will help ecologists and biologists decrease the costs of taking the data required for their scientific studies. The studies typically relate to the health of aquatic species, but can be amphibians or other species, as well.

    I can not post the code I used, but here is a simple description:
    I had 2 COGs running:
    - COG 1 was the "foreground" process
    It initiated the measurement by starting the second COG and the waited for it to finish and use thed result
    - COG 2 did the actual time critical measurement using WAITPNE. When it finished it wrote the result and also a status flag into a variable in HUB RAM

    When COG 1 had initiated the measurement and waited for COG 2 to finish it also had a timeout running. If this expired it just did a COGSTOP for COG 2. This also meant the measurement had failed.

  • hi rbehm - thanks, yes that makes sense. I should be able to figure out how to do that with more reading.

    jmg (or others), do you think that rbehms' approach would allow me to increase resolution from the .277mm?

    jmg, I'm not sure I'm completely following there, would you clarify what LSB is and also why you have the ' after the mm numbers? sounds like you know these sensors better than I do.

    thanks again
  • kwinnkwinn Posts: 8,697
    chris_bfs wrote: »
    hi rbehm - thanks, yes that makes sense. I should be able to figure out how to do that with more reading.

    jmg (or others), do you think that rbehms' approach would allow me to increase resolution from the .277mm?
    Yes, this approach would increase the resolution to 0.03472' mm.
    jmg, I'm not sure I'm completely following there, would you clarify what LSB is and also why you have the ' after the mm numbers? sounds like you know these sensors better than I do.

    LSB is "Least Significant Bit", and the resolution of the LSB ( .277mm vs .03472mm) determines the resolution of your measurement.
  • thanks kwinn - I was guessing that was the case, but wasn't sure what the link to the LSB was. If we were talking about an A-D converter, it would make more sense to me. In the case of counting clock cycles, I'm having trouble visualizing where a LSB comes into play. If you feel like educating me, please do.

    I think the ~.25mm resolution is okay for now.. good to know there's a method for improving that.
  • AribaAriba Posts: 2,690
    edited 2018-04-25 17:22
    Here is an improvement to my previous modifications.
    Now it uses a hardware counter to measure the exact pulsewidth. So you get the full resolution, but with the timeout.
    '
    '  propeller code to return magnet position as a function of
    '  MCU clock cycles for the Temposonics EP2D linear position
    '  sensor.
    '
    '
    '  LIMITATIONS:
    '
    '  This function returns the position of the first magnet only.
    '  (Magnet nearest head of sensor)
    '
    '  This function assumes the reference magnet is affixed to the
    '  end of the sensor.  It will time out (and fail to loop) if
    '  there is no magnet on the sensor. THE CODE SHOULD BE CHANGED
    '  TO WAIT A FIXED AMOUNT OF TIME THEN LOOP IF NO VALUE IS
    '  RETURNED.  In practice this has not been a problem except when
    '  running on a breadboard.
    '
    
    '
    
    VAR
      long pulseWidth
    
    PUB start
    
    '' Start a cog with the measurement routine
    
      return cognew(@entry,@pulseWidth)
    
      
        
    PUB getPosition
    
    '' Return the elapsed time (in clock ticks) between start and stop pulses
    
       pulseWidth := 0                              ' Use zero to indicate no pulse seen yet
       repeat until pulseWidth                      ' Wait until this changes to non-zero
       return pulseWidth                            ' Return the new value
    
    PUB getPositionNoWait
      return pulseWidth
    PUB clearPosition
      pulseWidth := 0
    
        
    DAT
    entry   org
    
    init    mov       t1,   #1      wz              '  configure start pin as output
            shl       t1,   startPin
            muxz      outa, t1                      '  set it LOW
            muxnz     dira, t1
            mov       t2,   #1      wz              '  configure stop pin as input
            shl       t2,   stopPin
            muxz      dira, t2
    
            movi      ctra, #%01100_000             ' Use counter in NEGDET mode to measure low pulse width
            movs      ctra, stopPin
            mov       frqa, #1
            
            mov       sampTime, cnt                 ' Set up delay
            add       sampTime, #9                  ' Add 9 clk cycles - time needed to execute this block
            waitcnt   sampTime, sampDelay
            
    loop    or        outa, t1                      ' Set the startPin high
            nop
            nop
            nop
            nop
            nop
            nop                                     '  series of NOP's to send 1.5 us pulse
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            andn      outa, t1                      ' Set startPin low
            
            nop
            nop
            nop
            nop
            nop
            nop                                     '  series of NOP's to wait for bogus STOP to go low
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            nop
            mov       phsa,#0                       ' start pulse time measure (stopPin is low here)
    
            mov       tempvar,timeout
    WtStp   test      t2,ina            wc          ' Wait for stopPin to go high
     if_nc  djnz      tempvar,#WtStp
            mov       endTime,phsa                  ' Get the exact pulse time value from the counter
            add       endTime,#42*4                 ' Calculate elapsed time inclusive all the nops before (if needed)
            test      t2,ina            wc          ' Sensor or timeout?
     if_nc  mov       endTime,#1                    ' Value that indicates timeout       
            wrlong    endTime,PAR                   ' Return the elapsed time
            waitcnt   sampTime, sampDelay           ' Pause - sensor sampling freq is ~40Hz
            jmp       #loop                         ' Loop
    
    zeroes        long      0                      
    stopPin       long      24                      
    startPin      long      25                      
    sampDelay     long      2_500_000               ' delay in clock cycles between sensor samples
    timeout       long      200_000                 ' 20ms
    startTime     res       1                       ' Start pulse system clock
    endTime       res       1                       ' Stop pulse system clock (and calculated difference)
    t1            res       1
    t2            res       1
    trigTime      res       1
    sampTime      res       1
    tempvar       res       1
    
    (it's for sure untested)

    Andy
  • Fantastic, thanks Andy - I'll give it a try and report!
  • kwinnkwinn Posts: 8,697
    chris_bfs wrote: »
    thanks kwinn - I was guessing that was the case, but wasn't sure what the link to the LSB was. If we were talking about an A-D converter, it would make more sense to me. In the case of counting clock cycles, I'm having trouble visualizing where a LSB comes into play. If you feel like educating me, please do.

    I think the ~.25mm resolution is okay for now.. good to know there's a method for improving that.

    ADC or Counter, the significance of a bit comes into play. For a 12 bit ADC or a 12 bit counter the LSB represents 1/4096 of the maximum possible value, and the MSB 2048/4096 or 1/2 of the maximum possible value.
Sign In or Register to comment.