Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
# ----------------------------------------------------------------------------
# servo_manager.py
# Class to manage and control a number of servos
#
# The MIT License (MIT)
# Copyright (c) 2020 Thomas Euler
# 2020-01-03, v1
# 2020-08-02, v1.1 ulab
# ----------------------------------------------------------------------------
import time
from machine import Timer
import machine
import ulab as np
from micropython import alloc_emergency_exception_buf
alloc_emergency_exception_buf(100)
__version__ = "0.1.1.0"
RATE_MS = const(20)
_STEP_ARRAY_MAX = const(500)
# ----------------------------------------------------------------------------
class ServoManager(object):
"""Class to manage and control a number of servos"""
TYPE_NONE = const(0)
TYPE_HORIZONTAL = const(1)
TYPE_VERTICAL = const(2)
TYPE_SENSOR = const(3)
def __init__(self, n, max_steps=_STEP_ARRAY_MAX, verbose=False):
""" Initialises the management structures
"""
self._isVerbose = verbose
self._isMoving = False
self._nChan = max(1, n)
self._Servos = [None]*n # Servo objects
self._servo_type = np.array([0]*n, dtype=np.uint8) # Servo type
self._servo_number = np.array([-1]*n, dtype=np.int8) # Servo number
self._servoPos = np.array([0]*n) # Servo pos [us]
self._SIDList = np.array([-1]*n, dtype=np.int8) # Servos to move next
self._targetPosList = np.array([0]*n) # Target pos [us]
self._currPosList = np.array([-1]*n) # Current pos [us]
self._stepSizeList = np.array([0]*n) # .. step sizes [us]
self._stepLists = [] # only for parabolic
for i in range(n):
self._stepLists.append(np.array([0]*max_steps))
self._nToMove = 0 # # of servos to move
self._dt_ms = 0 # Time period [ms]
self._nSteps = 0 # # of steps to move
self._Timer = Timer(0)
self._Timer.init(period=-1)
def add_servo(self, i, servoObj, pos=0):
""" Add at the entry `i` of the servo list the servo object, which has to
define the following functions:
- `write_us(t_us)`
- `angle_in_us(value=None)`
- `off()`
- `deinit()`
"""
if i in range(self._nChan):
self._Servos[i] = servoObj
self._servoPos[i] = servoObj.angle_in_us()
self._servo_number[i] = i
if self._isVerbose:
print("Add servo #{0:-2.0f}, at {1} us"
.format(i, int(self._servoPos[i])))
def set_servo_type(self, i, type):
""" Change servo type (see `TYPE_xxx`)
"""
if i in range(self._nChan) and self._Servos[i] is not None:
self._servo_type[i] = type
def turn_all_off(self, deinit=False):
""" Turn all servos off
"""
for servo in self._Servos:
if not servo is None:
servo.off()
if deinit:
servo.deinit()
def deinit(self):
""" Clean up
"""
self._Timer.deinit()
self.turn_all_off(deinit=True)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
'''
@timed_function
def move_timed(self, servos, pos, dt_ms=0, lin_vel=True):
self.move(servos, pos, dt_ms)
'''
@micropython.native
def move(self, servos, pos, dt_ms=0, lin_vel=True):
""" Move the servos in the list to the positions given in `pos`.
If `dt_ms` > 0, then it will be attempted that all servos reach the
position at the same time (that is after `dt_ms` ms)
"""
print('target pos {}'.format(pos))
# print(servos) # JD debugging
if self._isMoving:
# Stop ongoing move
self._Timer.init(period=-1)
self._isMoving = False
# Prepare new move
# do we still need nSteps???
nSteps = dt_ms /RATE_MS # RATE_MS=20
# print('nSteps_{}=dt_ms_{}/RATE_MS_{}'.format(nSteps,dt_ms,RATE_MS))
# by default ???? linear or parabolic?
if nSteps > _STEP_ARRAY_MAX: # 500
# Too many steps for a paraboloid trajectory
print("nSteps more than 500, use linear moving pattern")
lin_vel = True
print("WARNING: {0} is too many steps; going linear".format(int(nSteps)))
for idxS, SID in enumerate(servos):
# idxS == SID due to:
# SRV_PAN = const(0)
# SRV_TLT = const(1)
if not self._Servos[SID]:
continue
## carry on the value from Scanner class to SM class
self._SIDList[idxS] = SID
# set the target position for respective servos
self._targetPosList[idxS] = self._Servos[SID].angle_in_us(pos[idxS])
if nSteps > 0:
# A time period is given, therefore calculate the step sizes for this
# servo's move, with ...
p = self._servoPos[SID] # acquire the initial positions
dp = self._targetPosList[idxS] -p # calculate the displacement
# print('servo{},initial_pos(us):{}, target_pos(us):{},total displacement(us):{}'.format(
# idxS,p,self._targetPosList[idxS],dp))
if lin_vel:
# ... linear velocity
s = round(dp/RATE_MS) # why do we need this?
self._currPosList[idxS] = p +s # the next state
self._stepSizeList[idxS] = s # delta_displacement per step == stepsize
# print('linear velocity(us/step)={},nextPos={},_servoPos={}'.
# format(s,self._currPosList[idxS],self._servoPos[idxS]))
else:
# ... paraboloid trajectory
print('parabolic trajectory')
p_n = nSteps -1
p_n2 = p_n /2
p_peak = p_n2**2
p_func = [-(j +1 -p_n2)**2 +p_peak for j in range(int(nSteps))]
p_scal = dp /sum(p_func)
for iv in range(p_n -1):
self._stepLists[idxS][iv] = int(p_func[iv] *p_scal)
self._stepSizeList[idxS] = 0
else:
## if nStep==0
## Move directly, therefore update already the final position
self._servoPos[SID] = self._targetPosList[idxS]
self._nToMove = len(self._Servos)# n #
self._dt_ms = dt_ms
self._nSteps = int(nSteps) -1 # why -1????
## done setting up
## start moving
if dt_ms == 0:
## equivalent to nSteps =0
# move the servo directly to the targetPositions
for idxS in range(len(self._Servos)):
target_p = int(self._targetPosList[idxS])
## the code that actually does the move
self._Servos[self._SIDList[idxS]].write_us(target_p)
else:
# Setup timer to keep moving them in the requested time
## trigger _cb() every 20ms
self._Timer.init(mode=Timer.PERIODIC, period=RATE_MS, callback=self._cb)
self._isMoving = True
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
def _cb(self, value):
if self._isMoving:
# Update every servo in the list
for idxS in range(self._nToMove):
SID = self._SIDList[idxS]
for step in range(self._nSteps+1):
p = int(self._currPosList[idxS])
# print('debug,step_{},p_{}'.format(step,p))
if p >= self._targetPosList[idxS]:
# the next pos is beyond target
target_p = int(self._targetPosList[idxS])
# set the servo to target position
self._servoPos[SID] = target_p
self._Servos[SID].write_us(target_p)
self._isMoving = False
# print('stopped at {}/{} step'.format(step+1,self._nSteps+1))
break
else:
self._Servos[SID].write_us(p)
if self._stepSizeList[idxS] == 0:
## WHY ==0 ?-> paraboloid????
# Paraboloid trajectory
# print('self._stepSizeList[idxS]==0, paraboloid!!!!')
self._currPosList[idxS] += self._stepLists[idxS][self._nSteps]
else:
# Linear trajectory
# set the next position
self._currPosList[idxS] += self._stepSizeList[idxS]
# if step%10 ==0:
#print("linear, iter: {}/50 step, at {}, next {} ".format(
# step,p,self._currPosList[idxS]))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@property
def is_moving(self):
""" Returns True if a move is still ongoing
"""
return self._isMoving
# ----------------------------------------------------------------------------
a57c54f9fThis file is pinned to the selected release. Download the original to open it in your design tool.