Servo

Servo.
An I2C operated servo controller (not only for Lego-NXT).

Features:

  • easy to build (low pin count, through hole, single sided layout)
  • standard components (if able to prog PICs)
  • I2C controlled
  • good performance (considering 8MHz internal RC-oscillator)

If you need quick and relatively "precise" movements, a servo controller could be what you need.

Although there exist a few commercial I2C solutions, buying ready-to-use electronics is somehow a lack of style (and a waste of money in some cases too)...
This circuit is cheap (~3 Euro) and easy to reconstruct. Only a PIC 16F819 and a few other, passive components are needed.

I don't want to waste any time on theory of servo operation, so just a few lines:

A standard analog servo has its mid-position on a pulse/pause ratio of ~1.5ms/20ms.
Decreasing the pulse length down to ~1ms corresponds to a ~-45 shift, going up to 2ms will cause a ~+45 movement.

Depending on your servo type (and quality ;-) you may(!) extend the range of movement by up to 80-90%"
(Usual RC-equipment (like my Futaba T10C or my old FC-16) allow not more than +-20%).

***WARNING***
Especially digital servos, which internally operate with a much higher repetition rate, will draw huge currents if their movement is blocked!
This will damage gears as well as the motor!
You have been warned!

Keeping angles within +-55 is always safe, but sometimes you need more ;-)
This little servo controller allows you to use the full range of any servo.

These are the PICs internal values (as received from I2C):

;## SERV OUTPUTS (in ms):
;
;         +----+-----+ - - +                  +---------+
;         |          |                        |         |
;         |    |     |     |                  |         |
;         |          |                        |         |
;         |    |     |     |                  |         |
;         |          |                        |         |
;  -------+    + - - +-----+------------------+         +-----
;    1.0 >|----|<    |     |                  |
;         |          |     |                  |
;         |   1.5    |     |                  |
;         |<-------->|     |                  |
;         |                |                  |
;         |      2.0       |                  |
;         |<-------------->|                  |
;         |                                   |
;         |               25.0                |
;         |<--------------------------------->|
;
;
; Values must be 1 - 180 (other values may not work as expected ;-).
;   1 ->  500us pulse (over left)
;  45 -> 1000us pulse (left)
;  90 -> 1500us pulse (mid)
; 135 -> 2000us pulse (right)
; 180 -> 2490us pulse (over right)
;
			

Standard left/right values. Click to enlarge.


Insane left/right values. Click to enlarge.


I used a ~25ms repetition rate. Click to enlarge.

Although possible, it is NOT RECOMMENDED to power servos from the NXT!
To limit misuse, I added a 10E resistor. This will power 1 servo of the D47/FS31/SD100 class.

Use an external 4xNiMH pack, this will work with every servo available.
Do not care about the external oscillator, it is not required for this limited version of the servo controller.

Click schematic to enlarge.

Do not forget to apply the 2 wire-wraps! Click to enlarge.


Some pbLua code to try:

--
-- !!! pbLua v18a (or newer) required !!!
--
-- ServoDingsbums v0.7
--
--
-- changes v0.7:
--   - updates for pbLua v18a (!)
--
--
-- As usual: No error checks...
--
--
-- No nifty "classes", this time.
--
-- Just a quick hack...
-- 2009, ASkr
--



asPort=1   -- default; will get overwritten by 'asServoInit()'



--***************************************************************************
--** wait(<tim>)
--**
--** Waits for ~<tim> ms
--***************************************************************************
function wait(tim)
  local tim1=nxt.TimerRead()
  repeat
  until nxt.TimerRead() > tim+tim1
end



--***************************************************************************
--** asServoInit(port)
--**
--** <port> -> port to initialize for servo controller
--***************************************************************************
function asServoInit(port)
  asPort=port
  nxt.InputSetType(port,10)  -- NEW: pbLua v18a; I2C is now type "10dec"
  nxt.InputSetDir(port,1,1)
  nxt.InputSetState(port,1,1)
  nxt.I2CInitPins(port)
end



--***************************************************************************
--** asServoControl(<channel>,<value>)
--**
--** <channel> ->  0-5
--** <value>   -> standard: -45 - +45; 0 is mid
--**              insane:   -89 - +90; 0 is mid
--** For raw values, as accepted by the PIC, add +90 digits to these values,
--** like shown below (**3**)
--**
--** CONSECUTIVE CALLS TO THIS ROUTINE (or any other sending stuff you
--** invent) MUST NOT BE FASTER THAN 30ms!
--** Otherwise, I2C communication may not function properly...
--***************************************************************************
function asServoControl(channel, value)

  -- SAFE VALUES (standard)
  if value < -45 then value = -45 end
  if value >  45 then value =  45 end

  -- INSANE VALUES (recommended ;-)
--  if value < -89 then value = -89 end
--  if value >  90 then value =  90 end

  if channel < 0 or channel > 5 then return 0 end

  local tim=nxt.TimerRead()
  local i

  -- wait for I2C to become ready
  while ({nxt.I2CGetStatus(asPort)})[1] ~= 0 do
    if nxt.TimerRead() > tim+3000 then
--      print("***TIMEOUT (1)***")
      return 0
    end
  end

  tim=nxt.TimerRead()

--  for i=1,5
  while 1
  do

    -- (**3**) read note above
    -- !!! KEEP RAW VALUE (incl +90) >0 and <=180 !!!
    nxt.I2CSendData(asPort,string.char(0x30,channel,value+90))

    while 1 do
      local a,b,c=nxt.I2CGetStatus(asPort)

      if nxt.TimerRead() > tim+500  then
--        print("***TIMEOUT (2)***")
        return 0
      end

      if b==255 then
        wait(4) -- do NOT change this!!!
        break
      end

      if a==0 then return 0 end

    end -- while (timeouts)

  end -- while (no success)

  return 1

end



-- Just a few quick and dirty testing routines, from here down.



--***************************************************************************
--**
--**
--**
--***************************************************************************
function asServoTest()

  for i=1,200,1
  do
    print(i.." "..asServoControl(0,-45))
    wait(30)
    print(i.." "..asServoControl(0,45))
    wait(30)
  end
end



--***************************************************************************
--**
--**
--**
--***************************************************************************
function asServoStress()

  for i=1,1200,1
  do
    asServoControl(0,-45)
    wait(30) -- absolute minimum
  end
end



--***************************************************************************
--**
--**
--**
--***************************************************************************
function asServoMove()

  local i
  local j

  for i=1,5
  do
    wait(100);
    for j=-45,45,2
    do
      asServoControl(0,j)
      wait(30)
    end
  end
end



asServoInit(2)
asServoControl(0,0)

If your PIC programmer does not support config values in HEX file, here they are:




Mhh, this is not complete yet. Some ideas to combine Lego and Fischertechnik...

I planned extensions for default values in EEPROM as well as automatically controlled servo movement by pin inputs. This is not yet (and may be will never be) implemented.

ASkr 4/2009:
This project left me somehow unsatisfied:
A central distribution point for cabling 6 servos is sometimes unhandy.
Well, I invented something better: ServoBus.

Includes:
- schematic (PDF)
- placement (PDF)
- layout, eagle (BRD)
- sample pbLua code

DOWNLOAD: servo.zip


ASkr 12/2008