Absolute rotory encoder for linear measurement

reywas

Member
Hi all,

First a little project background info (perhaps TMI?):

I have undertaken a project to add a digital scale to my Wood-Mizer portable sawmill. Others have done this using DRO's (digital read-outs) but I want to go a different route because the DRO's invariably fail after just a few months due to the harsh environmental conditions inherent with a sawmill...vibration, dust, moisture. Besides, where's the challenge in buying something off of eBay? My particular mill already uses a rotary encoder to provide vertical movement information to a rather basic PIC-based setworks device that is used to make the drops for the desired board thickness, but it does not provide any absolute blade height information. A physical scale and pointer are used for that. My plan is to read the signal from the existing rotary encoder using a PICAXE to tally the total count and use another PICAXE to convert the count to a measurement and output it to an LCD.

I was surprised to find that it is an absolute encoder...I was expecting an incremental encoder. Having read through at least a dozen threads on this forum about incremental encoders, I already had a plan in mind for accomplishing the task :rolleyes:. I found the technical info for the encoder here:

http://usdigital.com/products/encoders/absolute/rotary/kit/MAE3

The one I have is the 10-bit, PWM (1KHz) output version. At first I had no idea how to track the direction and multiple rotations of the encoder, but after giving it some thought I came up with a scheme that seems to work:

read encoder (NewPos) and compare with previous position (PrevPos)

if NewPos > PrevPos and the difference is small(TBD), then rotation is CCW (anti-CW;))

if NewPos > PrevPos and the difference is large(TBD), then rotation is CW through the zero position

repeat for NewPos < PrevPos...​

Here's my first attempt at coding this, which works when turning encoder slowly by hand:

Code:
[color=Green];PICAXE-18M2[/color]
[color=Blue]setfreq m32
symbol [/color][color=Purple]NewPos [/color][color=DarkCyan]= [/color][color=Purple]w2[/color]
[color=Blue]symbol [/color][color=Purple]PrevPos [/color][color=DarkCyan]= [/color][color=Purple]w3[/color]

[color=Blue]symbol [/color][color=Purple]Offset [/color][color=DarkCyan]= [/color][color=Purple]w5            [/color][color=Green];holds the difference between PrevPos and NewPos[/color]
[color=Blue]symbol [/color][color=Purple]Cnt [/color][color=DarkCyan]= [/color][color=Purple]w4
Cnt [/color][color=DarkCyan]= [/color][color=Navy]8750                    [/color][color=Green];set measurement count initially to 1 inch[/color]

[color=Black]main:[/color]

[color=Blue]pulsin c.1[/color][color=Black],[/color][color=Navy]1[/color][color=Black],[/color][color=Purple]NewPos[/color]
[color=Blue]if [/color][color=Purple]NewPos [/color][color=DarkCyan]> [/color][color=Navy]820 [/color][color=Blue]then          [/color][color=Green];prevent erroneous readings that can occur at 0-position 
      [/color][color=Purple]NewPos [/color][color=DarkCyan]= [/color][color=Navy]1[/color]
[color=Blue]endif[/color]

[color=Green];debug:pause 4000:goto main[/color]

[color=Blue]select case [/color][color=Purple]NewPos
      [/color]
[color=Blue]case [/color][color=DarkCyan]= [/color][color=Purple]PrevPos                [/color][color=Green];if no position change do nothing
      
      [/color]
[color=Blue]case [/color][color=DarkCyan]> [/color][color=Purple]PrevPos
      Offset [/color][color=DarkCyan]= [/color][color=Purple]NewPos [/color][color=DarkCyan]- [/color][color=Purple]PrevPos
      [/color][color=Blue]if [/color][color=Purple]Offset [/color][color=DarkCyan]< [/color][color=Navy]410 [/color][color=Blue]then                            
            [/color][color=Purple]Cnt [/color][color=DarkCyan]= [/color][color=Purple]Cnt [/color][color=DarkCyan]+ [/color][color=Purple]NewPos [/color][color=DarkCyan]- [/color][color=Purple]PrevPos [/color][color=DarkCyan]MIN [/color][color=Navy]0        [/color][color=Green];CCW not thru 0
      [/color][color=Blue]else
            [/color][color=Purple]Cnt [/color][color=DarkCyan]= [/color][color=Purple]Cnt [/color][color=DarkCyan]- [/color][color=Navy]820 [/color][color=DarkCyan]+ [/color][color=Purple]NewPos [/color][color=DarkCyan]- [/color][color=Purple]PrevPos [/color][color=DarkCyan]MIN [/color][color=Navy]0  [/color][color=Green];CW thru 0
      [/color][color=Blue]endif

      
case [/color][color=DarkCyan]< [/color][color=Purple]PrevPos                                        
      Offset [/color][color=DarkCyan]= [/color][color=Purple]PrevPos [/color][color=DarkCyan]- [/color][color=Purple]NewPos
      [/color][color=Blue]if [/color][color=Purple]Offset [/color][color=DarkCyan]< [/color][color=Navy]410 [/color][color=Blue]then
            [/color][color=Purple]Cnt [/color][color=DarkCyan]= [/color][color=Purple]Cnt [/color][color=DarkCyan]+ [/color][color=Purple]NewPos [/color][color=DarkCyan]- [/color][color=Purple]PrevPos [/color][color=DarkCyan]MIN [/color][color=Navy]0        [/color][color=Green];CCW thru 0       
      [/color][color=Blue]else        
            [/color][color=Purple]Cnt [/color][color=DarkCyan]= [/color][color=Purple]Cnt [/color][color=DarkCyan]+ [/color][color=Navy]820 [/color][color=DarkCyan]-[/color][color=Purple]PrevPos [/color][color=DarkCyan]+ [/color][color=Purple]NewPos [/color][color=DarkCyan]MIN [/color][color=Navy]0   [/color][color=Green];CW not thru 0
      [/color][color=Blue]endif
      
endselect

debug[/color][color=Black]:[/color][color=Blue]pause [/color][color=Navy]4000[/color]
[color=Purple]PrevPos [/color][color=DarkCyan]=  [/color][color=Purple]NewPos[/color]
[color=Blue]goto [/color][color=Black]main[/color]
FYI:
The shaft is turning at 16 RPS, which by my math translates to 62500uS per revolution.
The PWM duty cycle is 1025uS, which gives a max pulsin count of 820 running at 32Mhz

Questions:

  1. On line 21 of the code I would like to test NewPos against (PrevPos +/- 2). How can I do that?
  2. How can I calculate the sampling rate per revolution (PWM duty cycle plus code execution time)?
  3. Is there a better, faster way to to code this?
  4. Using this scheme, should I still plan on using a second PIC for the LCD?


Thanks for your help with this.
 

hippy

Technical Support
Staff member
Because your reading is less than 16-bit I think you can use the trick of when 'now' is less than 'last, increment 'now' by whatever 'maximum plus one is'. Then subtract 'last' from 'now' and if the result is greater than 'half ( maximum plus 1), it's going backwards, otherwise going forward.

This avoids the complexities of two's complement maths.

Try running this in the PE6 simulator. It uses readings of 0...999 rather than 0..1023 just to make it clearer to see how it works.

Code:
#Picaxe 08M2
#Terminal 4800

symbol MAXIMUM             = 999
symbol MAXIMUM_PLUS_1      = MAXIMUM + 1
symbol HALF_MAXIMUM_PLUS_1 = MAXIMUM_PLUS_1 / 2

Symbol now  = w0
Symbol last = w1
Symbol diff = w2

last =   0 : now = 100 : Gosub Calc
last = 100 : now = 400 : Gosub Calc
last = 400 : now = 600 : Gosub Calc 
last = 600 : now = 800 : Gosub Calc
last = 800 : now =   0 : Gosub Calc
last =   0 : now = 800 : Gosub Calc
last = 800 : now = 600 : Gosub Calc
last = 600 : now = 400 : Gosub Calc
last = 400 : now = 100 : Gosub Calc
last = 100 : now =   0 : Gosub Calc
SerTxd( CR, LF )
last = 900 : now = 100 : Gosub Calc
last = 100 : now = 900 : Gosub Calc
SerTxd( CR, LF )
last =   0 : now = 999 : Gosub Calc
last = 999 : now =   0 : Gosub Calc
End

Calc:
  SerTxd( #last, 9, #now, 9 )
  If now < last Then
    now = now + MAXIMUM_PLUS_1
  End If
  diff = now - last
  If diff > HALF_MAXIMUM_PLUS_1 Then
    diff = MAXIMUM_PLUS_1 - diff
    SerTxd( "-", #diff, CR, LF )
  Else
    SerTxd( "+", #diff, CR, LF )
  End If
  Return
 
Last edited:

AllyCat

Senior Member
Hi,

It seems that you need to keep track of both complete revolutions and a residual partial revolution, which might not be too easy with the method you're using. Also, I don't like the "hard coded" 410 and 820 which might not really be "constants". The way I would do it is to scale the value so that 1 revolution is represented by 65536 (or 256 might be easier to understand, but may have inadequate resolution). Conceptually the value can be considered as a "binary fraction", i.e. the Most Significant Bit (of the byte, word or any other number of bits) represents half a revolution, the next bit a quarter rev, the next 1/8, etc.

But to answer your questions:

1. If you subtract newpos from oldpos (or vice versa) you will get a signed value. If you want to "do nothing" if they are within +/-2 then add 2 and then: IF value <= 4 THEN donothing .

2. IMHO this is where you will likely have problems with PULSIN because it is "blocking", so the rest of the program may be "locked out" for almost a full revolution. Also, PICaxe basic is rather slow, so typically each (simple) instruction is equivalent to a count of around 100 (and a SELECT.. CASE perhaps very much more).

3. It depends on the range of shaft speeds, but I think the way I would do it is to low-pass filter the PWM and read the shaft position using a READADC (which takes no longer than most normal instructions). If the ADC and PWM use the same "reference" voltage, that will also give you a directly scaled fractional binary value (e.g. multiply a READADC10 value by 64). The LP filter may need careful design and at running speed you may need to compensate for lag through the filter.

4. It depends on the LCD. If it already has its own PIC(axe) driver to receive ASCII values, then communication via HSEROUT is probably fast enough. If it's just a parallel-controlled LCD (or worse, an I2C expander using a 4-bit data interface) then you probably should assume the need to add a second processor.

Cheers, Alan.

EDIT:

With hindsight, I think my reply above is over-pessimistic. The shaft is rotating quite slowly (in electronics terms), you only need to "sample" the position so the program execution times may not be an issue. Therefore, a second PICaxe shouldn't be necessary and perhaps not even an elevated clock frequency.

However, there may be some issues with the accuracy and detection of the shaft position around the "zero phase" position, particularly if the encoder can generate 0% and/or 100% "pulse" widths (i.e. dc). In this situation, PULSIN may return an "out of range" value, or even timeout (after blocking the main program until a count of 65536 is reached).

Also, there may be variations in the clock frequency of the PICaxe and/or the encoder, so I would "calibrate" the PWM period by measuring and adding the High and Low pulse periods (when the shaft is stationary). Then, converting the duty cycle to a Binary Fraction (as above) is a powerful (and ultimately simple) technique, because the wrap-around at zero phase becomes "automatic", so no special code is required.

FWIW (probably not necessary for this project), more "difficult" pulse/PWM measurements can be made by using the "Timer 1 Gate" hardware in most PIC(axe)s. This is able to measure shorter pulse widths, or the period in a single measurement, and as a "background" (i.e. non-blocking) task. Therefore, when allocating a pulse-measuring input pin, it can be useful to select the T1G input, which is Leg 6 on an 18M2 and Leg 3 or Leg 4 (selectable) with other M2s.
 
Last edited:

hippy

Technical Support
Staff member
I had a thought. It is not by how much the sensor has moved we need to be measuring and tallying, but more simply whether it has crossed its 0 degree point, and in which direction.

That's quite easy, and is similar to what I said above. Split the reading range into quarters, then -

If last is in the top quarter, and now is in the bottom; it crossed 0 degree in one direction

If last is in the bottom quarter, and now is in the top; it crossed 0 degree in the other

Linear distance is then '( ( revolutions * K ) + now ) * scaling'.

The division of readings isn't actually quarters but is really unequal thirds. Top and bottom thirds the same size, the middle larger than top and bottom. I think the optimal is when mid = top+1 and bottom+1. For example if readings were 0 to 9 -

Code:
                          .-----.
                         \|/    |
   .-----.-----.       .-----.-----.       .-----.-----.
5 6|7 8 9|0 1 2|3 4 5 6|7 8 9|0 1 2|3 4 5 6|7 8 9|0 1 2|3 4
   `-----^-----'       `-----^-----'       `-----^-----'
                          |    /|\
                          `-----'
That does impose limitations on the readings. The change from last to now can never be greater than 3 in the above example, eg 6+4=>0, 3-4=>9 will not be detected.

It is possible to have dynamically calculated divisions, but that requires additional calculations, takes time, and gets more complicated.
 

reywas

Member
Thanks for the help guys. I'm still digesting some of the information.

hippy:

I believe the code I posted does the same thing as your code, only in a much less elegant and slower way. I added a few lines to your code to keep a running tally and it seems to work perfectly (when rotating the shaft by hand). I actually had the same thought as you regarding counting revolutions. I think I will need to keep two counts: full_rev and part_rev. Once it is determined that 0 has been crossed, the full_rev count will be updated and full_rev + part_rev will be output to the LCD. This should eliminate the errors associated with tallying up partial revolutions.

"That does impose limitations on the readings. The change from last to now can never be greater than 3 in the above example, eg 6+4=>0, 3-4=>9 will not be detected."

This is also true regarding the code you posted (and my original code), except the difference must be less than half range. So it seems to me that the code should give accurate results so long as the sampling rate per revolution is greater than the divisor of the range. Am I correct in my thinking?

[HR][/HR]
Allen:

Would an "if/then/else" instruction be preferable to a "select case" where code speed is a concern?

I have seen the erroneous readings when the encoder is at the 0 position. I put in if NewPos > MaxRange, then NewPos = 1 to take care of that, but I'm not sure if it will be sufficient in all cases.

The encoder data sheet does recommend reading the total duty cycle for best accuracy but I haven't figured out how to read both the high and low pulse given that they share a common end/beginning point. Advice?

So I should convert the 1-820 pulsin range to 0-1024 (820 x 1.25 = 1025)? And then...? I must admit that I'm a bit rusty when it comes to binary math. Some remedial work is in order.

Thanks!
 
Last edited:

hippy

Technical Support
Staff member
So it seems to me that the code should give accurate results so long as the sampling rate per revolution is greater than the divisor of the range. Am I correct in my thinking?
That is correct.

Your code seems to be trying to calculate where a 'up to half a revolution' would take you which would allow the slowest sample rate. The 'up to a third of revolution' scheme requires a faster sampling rate.

Not having to do the calculations and more complicated comparisons will allow faster execution and a faster sampling rate to be achieved.

As it takes the shaft around 62ms to do a full revolution at fastest speed of drop it should be possible to sample at least three times during that period to keep everything tracking okay.
 

AllyCat

Senior Member
Hi,

AFAIK, the Program Editor converts SELECT .. CASE into an IF .. THEN type structure anyway, so there shouldn't be much difference in execution speed. However, there is a risk that it may create an "unexpected", non-optimal structure, so I usually check the number of bytes reported in a syntax check. If a particular code structure generates more bytes than another, then it may indicate that the code will take longer to execute. When I did measure actual execution times of certain PICaxe instructions, the SELECT... format appeared to execute rather slower than IF... THEN . But it's difficult to create "eqiuivalent" code structures for a definitive comparison.

No, PICaxe Basic cannot measure the period of adjacent High and Low pulses, so they must be read in two consecutive cycles (i.e. with PULSIN pin,0,.. and then PULSIN pin,1,..) which is why the shaft may need to be stationary.

In principle, if the angular rotation is "normalised" to 1024 steps, then individual movements can be added/subtracted and the higher bits (of the accumulated total) directly indicate the number of revolutions. This would be independent of the exact number of PWM steps per revolution (i.e. the sum of the two PULSIN values). The calculation for 820 is easy (i.e multiply by 5 and divide by 4), but for the general case we need to multiply by 1024 and divide by the measured period (i.e. total steps per revolution), which is rather a struggle with PICaxe maths. So maybe in your application it's easier just to maintain separate values for the numbers of complete-revolutions and part-revolutions and calculate the total movement "on the fly" when required.

Cheers, Alan.
 
Top