Shop OBEX P1 Docs P2 Docs Learn Events
Simple and cheap encoder idea — Parallax Forums

Simple and cheap encoder idea

pedwardpedward Posts: 1,642
edited 2013-10-16 10:18 in Propeller 1
What has 3 rotary encoders, 3 limit switches, costs $3 and already works with the Propeller?

A PS2 wheel mouse with ball.

I had this idea the other day that I could rob the parts from a ball-mouse and use them as feedback for a closed loop motion control system driven by the Prop. As luck would have it, I got a surplus ball-mouse from Church this afternoon.

When tearing apart disused inkjet printers, it's become a lot more common to see a servo controlled print head with linear encoder instead of stepper motor. The paper feed path is the same. This actually makes a lot of sense, the linear encoder has become necessary as resolution and speed has increased, and steppers are more expensive than little brushed DC motors.

It then occurred to me that I could take the wheels out of a mouse and drill them to fit the end of a ball screw, or a pulley on a belt.

The mouse driver on the Propeller performs 200 samples per second, 5ms resolution. That's pretty decent when you consider a speed like 100ipm is 60 x 100 x 200 = 1.2 million samples in that period of time. If you are using a 5TPI screw (very common) then you'd have 100 * 5 = 500 revolutions per minute and 1.2-e6 / 500 = 2400 samples per revolution.

I have a picture of the encoder wheel I'll post once I reboot (USB crashed because of some old crappy peripheral).

EDIT: Ok, I got the picture off my phone and edited it in GIMP to get something useful.

The encoder wheels in my mouse have 60 divisions per revolution. That's pretty low when you look at machine resolutions. That would amount to 60 * 5 = 300 counts per inch or 300 dpi (curious). That's about 3.3 thousandths per count, which isn't very high resolution.

Resolution could be easily increased by printing an encoder wheel on transparency material, then attaching this to a stiffer backing. You can also use scavenged parts from a printer, including the optical sensors originally used by the printer. A quick search on digikey returns this part: http://www.digikey.com/product-detail/en/EE-SX1131/OR628CT-ND/355301 which could be used for a custom sensor board.

The funny part is you could connect these directly to the Prop and avoid the mouse electronics, but the mouse provides information about delta instead of sampling directly, so you've got 3 encoder channels that are tracked by a "co-processor" that is the mouse chip.

It's a neat idea that might prove useful for applications where lower resolution isn't a problem (dog feeder?).
655 x 645 - 13K
655 x 645 - 12K
655 x 645 - 11K

Comments

  • pedwardpedward Posts: 1,642
    edited 2013-02-25 16:11
    I was thinking about this a bit more, and I came up with a really simple way of using this, and it makes use of another easy to interface peripheral: Servos.

    You could use the rotary encoder with either continuous or standard rotation servos. In standard rotation it would be a 6 degree resolution direct reading encoder. For continuous rotation it would be a simple fire-and-forget motion control.

    Let's say you have a belt driven carriage, like on a scanner or printer. You replace all of the gear reduction (with resultant lash stackup) with a direct drive continuous rotation servo, then put a printed encoder wheel on the pulley. Read the encoder wheel with the mouse guts (perhaps using a remote sensor scavenged from a printer) and you've got a very simple to interface motion control.

    The mouse keeps track of the absolute location, presumably 1 count per division. You locate home, reset the mouse, then use a loop that targets a specific mouse location with the servo output driven at a given speed, preferably all in a PID loop.

    You could reuse the linear encoders from a printer or print some.

    This solves the problem of having a motor driver and power source, since you just use a regular servo. Servos aren't particularly fast, but they're easy to interface and they're self contained.

    You could practically modify the Mouse driver to poll more than 1 mouse at a time, giving you multiples of 2 or 3 axes, depending on the mouse.

    Super low pin count and easy to interface to. When I get some time I'll knock together a test. I've got a short linear slide, leadscrew, servo, and encoders/sensors from printers.
  • tonyp12tonyp12 Posts: 1,951
    edited 2013-02-25 16:53
    If someone want the simplicity of printing reflective wheels or stripes, encoders start at $8 each
    http://www.mouser.com/Search/Refine.aspx?N=1323043&Keyword=AEDR&Ns=Pricing%7c0&FS=True
  • pedwardpedward Posts: 1,642
    edited 2013-02-25 18:25
    Perhaps my point about the mouse chip doing all of the work wasn't well conveyed. When you use any type of encoder directly, you have to write/run the firmware on the Prop to keep track of it. In this thread you can let the mouse chip keep track of the encoder and let the Prop get the location asynchronously. Tracking encoders with the prop is a rather less efficient use of resources. With a modified mouse driver, you could probably track a dozen encoders with 4 mice connected to 5 pins. You could then use the Servo_32v7 module to drive the axis motors.
  • rwgast_logicdesignrwgast_logicdesign Posts: 1,464
    edited 2013-02-25 23:57
    This sounds like a really cool idea. Im following it.. I think most any home brew optical encoder could be connected to the mouse chips. I have aboit three serial ball mice i scrapped. Ill definately keep my eye on this thread.

    Ive seen binary counters along with some other 74xx chips set up to do quadrature decoding, but this may be a simpler solution. I do how ever wonder if the mouse can keep track of a 1000 ticks per revalution, as this seems pretty common when discs are placed before gearing on a standard dc motor
  • tonyp12tonyp12 Posts: 1,951
    edited 2013-02-26 09:45
    If you want use ps2 to do the data crunching a reflective(optical) mouse will make it simpler to print a encoder wheel or stripe.
    http://www.ebay.com/itm/FAST-SHIP-iMicro-MO-1008P-Optical-PS-2-Mouse-Black-/190749169148?pt=Mice&hash=item2c698955fc
  • pedwardpedward Posts: 1,642
    edited 2013-02-26 10:52
    I suspect a reflective encoder wheel is much much lower resolution than a transmissive style. I just went through my parts bin of scavenged inkjet printers and pulled out a rotary encoder and linear encoder, with their respective reader boards.
  • pedwardpedward Posts: 1,642
    edited 2013-03-01 22:55
    I built a proof of concept of this idea, and man it works cool! The Parallax hobby servos aren't very fast, so they are a bit of a limitation with performance.

    Here's a video of the demo I made:

    [video=youtube_share;EUquqGbbxx4]
  • pedwardpedward Posts: 1,642
    edited 2013-03-02 00:51
    Here's some relevant code bits. I looked at and tried 2 of the PID objects from OBEX. The first I tried was simply "PID" and it had the nice feature of running in a dedicated COG and calculating the PID feedback synchronously, independent of the main control loop. The nice thing about that is your integral decay happens whether you're in a call or not, it's fire and forget. When you are using part of your main loop to calculate PID, then you have to keep track of time and apply the time delta weighting, which makes the code a little more complicated and ugly.

    The other PID is PID1_1 and I liked the thought put into scaling, fixed point math, and they tried to do some best effort upgrades to the Arduino code it was inspired by. The problem with this code was twofold: It has severe integral windup, making it very difficult to use properly, and there isn't any time delta weighting because the code it was copied from assumed you called it at least once per second (in the example).

    Without the time delta weighting, the decay rates of Integral and Derivative don't work properly, because it's not synchronously tied to a unit of time. I fixed this problem by keeping track of CNT and only processing the PID loop if at least 1ms had passed, making the maximum bandwidth of the algorithm 1Khz. I followed the code the ORIGINAL author wrote and just hardcoded the time delta at 1ms.

    For Integral windup, I took a simple approach, I only start processing the Integral if the error is within 20 counts, this prevents the Integral overshoot caused by windup. This could be made a tunable, but it works with the hard coded value. The loop runs as a PD controller until the error is within window, then it converts to PID. This seems to work nicely, even just P control was working pretty well with the test rig.

    Here's the modified PID routine:
    PUB PID_Compute(vInput)
    ' vInput is the process variable
    ' returns a scaled output
    ' Takes raw input in 100th degree and returns scaled and limited output
    
      PID_Input := vInput
    
      If PID_Auto
        'Compute all the working error variables
        PID_cnt := cnt
    
        if PID_lcnt > PID_cnt
          PID_tdelta := $FFFF_FFFF - PID_lcnt + PID_cnt
        else
          PID_tdelta := PID_cnt - PID_lcnt
    
        PID_tdelta := PID_tdelta / (clkfreq / 1000)
    
        if PID_tdelta >= 1
          PID_Error := PID_SP - PID_Input
          if ||PID_Error < 20
            PID_errSum += PID_Error
            PID_ITerm += (PID_KI * PID_errSum)
            PID_ITerm <#= PID_OutMax                                'Limit ITerm max to the OutMax
            PID_ITerm #>= PID_OutMin                               'Limit ITerm min to the OutMin
          else
            PID_ITerm := 0
    
          PID_Derr := (PID_Input - PID_LastInput)
    
          'Compute PID Output
          PID_Output := (PID_KP * PID_Error) + (PID_ITerm) - (PID_KD * PID_Derr)
          PID_Output <#= PID_OutMax                                'Limit ITerm max to the OutMax
          PID_Output #>= PID_OutMin                                'Limit ITerm min to the OutMin
    
          PID_LastInput := PID_Input
          PID_Lcnt := PID_cnt
    
        return PID_Output / PID_OutScale
    
      else
        return 0
    
    

    And here is my demo program:
    {{ PS2 encoder feedback to servo }}
    
    CON
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000                                'Note Clock Speed for your setup!!
    
      PS2_data = 27
      PS2_clock = 26
    
      Servo_P = 0
    
    OBJ
    
      mouse: "Mouse"
      servo: "Servo32v5"
      pst: "Parallax Serial Terminal"
      pid: "PID1_1"
    
    VAR
    long output 'PID Output
    
    PUB main
      mouse.start(PS2_data, PS2_clock)
      mouse.bound_limits(0, 0, 0, 32767, 32767, 0)
      mouse.bound_scales(1, 1, 0)
    
      servo.start
    
      pid.PID_Init
      pid.PID_SetOutLimits(-1000000,1000000,1000)                     'Output limits are before scaling, this will give a 0 - 200 output
      pid.PID_SetTunings(51,5,7)                             'Pick some good numbers for the P, I, D,
      pid.PID_SetState(true)                                  'Enable it.
    
      pst.start(115200)
    
      move(500)
      waitcnt(clkfreq / 10 + cnt)
      move(750)
      waitcnt(clkfreq / 10 + cnt)
      move(900)
      waitcnt(clkfreq / 10 + cnt)
      move(100)
      waitcnt(clkfreq / 10 + cnt)
      move(200)
      waitcnt(clkfreq / 10 + cnt)
      move(100)
    
      repeat
    
    PUB move(setpoint) |x
      pid.PID_SetSP(setpoint * 100)                                       'PID setpoint is before scaling eg, 25c
    
      repeat until x == setpoint
        x := ||mouse.abs_x
    
        output := pid.PID_Compute(x * 100)
    
        servo.set(Servo_P,output+1500)
    
      servo.set(Servo_P,1500)
    
    
  • pedwardpedward Posts: 1,642
    edited 2013-03-02 01:10
    With speed setpoint (like a G01 without full stop):
       move(500,1000)
    '  waitcnt(clkfreq + cnt)
      move(750,200)
    '  waitcnt(clkfreq + cnt)
      move(100,50)
      move(900,100)
    '  waitcnt(clkfreq + cnt)
      move(200,30)
    '  waitcnt(clkfreq + cnt)
      move(100,10)
      servo.set(0,1500)
    
      repeat
    
    PUB move(setpoint,speed) |x
      pid.PID_SetOutLimits(-speed*1000,speed*1000,1000)                     'Output limits are before scaling, this will give a 0 - 200 output
      pid.PID_SetSP(setpoint * 100)                                       'PID setpoint is before scaling eg, 25c
    
      repeat until x == setpoint
        x := ||mouse.abs_x
    
        output := pid.PID_Compute(x * 100)
    
        servo.set(0,output+1500)
    
  • Sumeet66Sumeet66 Posts: 2
    edited 2013-10-16 10:18
    Hi pedwaed i implemented ur code for my project but the output remains constant even when vInput values are changing. Please can u tell me why it is happening
Sign In or Register to comment.