Hekateros  3.4.3
RoadNarrows Robotics Robot Arm Project
hekCalibStretch.cxx
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////////////
2 //
3 // Package: Hekateros
4 //
5 // Library: libhekateros
6 //
7 // File: hekCalibStretch.cxx
8 //
9 /*! \file
10  *
11  * $LastChangedDate: 2015-02-11 15:38:58 -0700 (Wed, 11 Feb 2015) $
12  * $Rev: 3868 $
13  *
14  * \brief HekCalibStretch - Hekateros calibration by stretching class
15  * implementation.
16  *
17  * \author Robin Knight (robin.knight@roadnarrows.com)
18  *
19  * \copyright
20  * \h_copy 2013-2017. RoadNarrows LLC.\n
21  * http://www.roadnarrows.com\n
22  * All Rights Reserved
23  */
24 /*
25  * @EulaBegin@
26  *
27  * Unless otherwise stated explicitly, all materials contained are copyrighted
28  * and may not be used without RoadNarrows LLC's written consent,
29  * except as provided in these terms and conditions or in the copyright
30  * notice (documents and software) or other proprietary notice provided with
31  * the relevant materials.
32  *
33  * IN NO EVENT SHALL THE AUTHOR, ROADNARROWS LLC, OR ANY
34  * MEMBERS/EMPLOYEES/CONTRACTORS OF ROADNARROWS OR DISTRIBUTORS OF THIS SOFTWARE
35  * BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
36  * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
37  * DOCUMENTATION, EVEN IF THE AUTHORS OR ANY OF THE ABOVE PARTIES HAVE BEEN
38  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39  *
40  * THE AUTHORS AND ROADNARROWS LLC SPECIFICALLY DISCLAIM ANY WARRANTIES,
41  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
42  * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN
43  * "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
44  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
45  *
46  * @EulaEnd@
47  */
48 ////////////////////////////////////////////////////////////////////////////////
49 
50 #include "rnr/rnrconfig.h"
51 
52 #include "Dynamixel/Dynamixel.h"
53 #include "Dynamixel/DynaServo.h"
54 
55 #include "Hekateros/hekateros.h"
56 #include "Hekateros/hekJoint.h"
57 #include "Hekateros/hekMonitor.h"
58 #include "Hekateros/hekCalib.h"
60 #include "Hekateros/hekRobot.h"
61 
62 using namespace std;
63 using namespace hekateros;
64 
65 // ---------------------------------------------------------------------------
66 // Private Interface
67 // ---------------------------------------------------------------------------
68 
69 /*!
70  * \brief Convenience macro for trying to get a servo object from dynamixel
71  * chain.
72  *
73  * Failure is considered a software bug since the chain has already be
74  * verified.
75  *
76  * Only works locally.
77  *
78  * \param [in] nServoId Servo id.
79  * \param [out] pServo Pointer to associated servo object.
80  *
81  * \return On failure, forces return from calling function with the appropriate
82  * error code.
83  */
84 #define HEK_TRY_GET_SERVO(nServoId, pServo) \
85  do \
86  { \
87  if( (pServo = m_robot.m_pDynaChain->GetServo(nServoId)) == NULL ) \
88  { \
89  LOGERROR("BUG: Servo %d: Cannot find in dynamixel chain.", nServoId); \
90  return -HEK_ECODE_INTERNAL; \
91  } \
92  } while(0)
93 
94 /*!
95  * \brief Calibration order by master servo id.
96  */
97 static const string CalibOrder[] =
98 {
99  "grip",
100  "wrist_pitch",
101  "wrist_rot",
102  "elbow",
103  "shoulder",
104  "base_rot"
105 };
106 
107 
108 // ---------------------------------------------------------------------------
109 // Class HekCalibStretch
110 // ---------------------------------------------------------------------------
111 
112 int HekCalibStretch::calibrate()
113 {
114  MapRobotJoints::iterator pos;
115 
116  string strJointName;
117  HekRobotJoint *pJoint;
118  int eLimitTypes;
119  size_t i;
120  int rc;
121 
122  // reset odometer to current arm pose
123  m_robot.m_pKin->resetServoOdometersForAllJoints();
124 
125  //
126  // Calibrate Hekateros joints in the specified calibration order.
127  //
128  for(i=0, rc=HEK_OK; (i<arraysize(CalibOrder)) && (rc >= 0); ++i)
129  {
130  strJointName = CalibOrder[i];
131 
132  // no joint in this product version with associated joint name
133  if( (pJoint = m_robot.getArmJoint(strJointName)) == NULL )
134  {
135  continue;
136  }
137 
138  // already calibrated
139  if( pJoint->m_eOpState == HekOpStateCalibrated )
140  {
141  continue;
142  }
143 
144  LOGDIAG2("\n----------- Calibrating Joint %s...", strJointName.c_str());
145 
146  eLimitTypes = pJoint->m_eLimitTypes;
147  pJoint->m_eOpState = HekOpStateCalibrating;
148 
149  // one or more top dead center electronic switch limits
150  if( eLimitTypes & HekLimitTypeElecTDC )
151  {
152  rc = calibrateJointTopDeadCenter(*pJoint);
153  }
154 
155  // one or more electronic switch limiters
156  else if( eLimitTypes & HekLimitTypeElec )
157  {
158  rc = calibrateJointByLimits(*pJoint);
159  }
160 
161  // physical limits
162  else if( eLimitTypes & HekLimitTypePhys )
163  {
164  rc = calibrateJointByTorqueLimits(*pJoint);
165  }
166 
167  // no detectable limits - assume current position is the good position
168  else if( eLimitTypes & HekLimitTypeNone )
169  {
170  rc = calibrateJointByTrust(*pJoint);
171  }
172 
173  else if( eLimitTypes & HekLimitTypeAbsEnc )
174  {
175  LOGWARN("Joint %s: Absolute encoders are not supported yet.",
176  strJointName.c_str());
177  }
178 
179  // warning
180  else
181  {
182  LOGWARN("Joint %s: Do not know how to calibrate this joint.",
183  strJointName.c_str());
184  }
185 
186  pJoint->m_eOpState = rc == HEK_OK? HekOpStateCalibrated:
187  HekOpStateUncalibrated;
188  }
189 
190  return rc;
191 }
192 
193 int HekCalibStretch::calibrateJointTopDeadCenter(HekRobotJoint &joint)
194 {
195  // joint calibrating velocity (radians/second)
196  static double TuneCalVel = degToRad(20.0);
197 
198  // quick try in the minimum direction (radians)
199  static double TuneMinAngle = degToRad(20.0);
200 
201  string strJointName; // joint name
202  int nServoId; // servo id
203  bool bIsReverse; // odometer is [not] reversed
204 
205  double fJointCurPos; // user's starting/current position (radians)
206  double fJointCurVel; // joint current velocity (radians/second)
207  double fJointGoalPos; // joint working goal position (radians)
208  double fJointCalibPos; // joint calibrated position (radians)
209 
210  int nServoCurPos; // servo current position (odometer ticks)
211  int nServoCurSpeed; // servo current speed (raw unitless)
212 
213  byte_t byOptMask; // optical limits mask
214  byte_t byOptBits; // optical limits bits
215  int nEdge; // which edge triggered
216 
217  int i; // working index
218  int rc = HEK_OK; // return code
219 
220  strJointName = joint.m_strName;
221  nServoId = joint.m_nMasterServoId;
222  bIsReverse = joint.m_nMasterServoDir == DYNA_DIR_CCW? false: true;
223 
224  LOGDIAG2("Joint %s (servo=%d): Calibrating by TDC optical limits.",
225  strJointName.c_str(), joint.m_nMasterServoId);
226 
227  //
228  // Only continuous joints support tdc limits
229  //
230  if( joint.m_eJointType != HekJointTypeContinuous )
231  {
232  LOGERROR("Joint %s (servo=%d): Not a continuous joint.",
233  strJointName.c_str(), joint.m_nMasterServoId);
234  return -HEK_ECODE_NO_EXEC;
235  }
236 
237  //
238  // Build optical limits bit mask.
239  //
240  for(i=0, byOptMask=0x00; i<HekOptLimitMaxPerJoint; ++i)
241  {
242  byOptMask |= joint.m_byOptLimitMask[i];
243  }
244 
245  //
246  // Sanity check
247  //
248  if( byOptMask == 0x00 )
249  {
250  LOGERROR("Joint %s (servo=%d): No optical limit bits set, spec correct?",
251  strJointName.c_str(), nServoId);
252  return -HEK_ECODE_INTERNAL;
253  }
254 
255  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
256  // Find joint top-dead-center position.
257  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
258 
259  LOGDIAG2("Joint %s (servo=%d): "
260  "Determining joint top-dead-center by optical limit(s).",
261  strJointName.c_str(), nServoId);
262 
263  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
264 
265  LOGDIAG2("Joint %s (servo=%d): Starting at uncalibrated %.2lf degrees.",
266  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
267 
268  // Never auto-stop at TDC limits.
269  joint.m_bStopAtOptLimits = false;
270 
271  byOptBits = getDarkOpticalLimits(m_robot.m_monitor.getJointLimitBits(),
272  byOptMask);
273 
274  //
275  // Starting in the light. First try a small degree of rotation in the
276  // minimum direction to find an optical limit dark edge.
277  //
278  if( byOptBits == 0x00 )
279  {
280  LOGDIAG2("Joint %s (servo=%d): "
281  "Determining joint optical limit light-to-dark edge.",
282  strJointName.c_str(), nServoId);
283 
284  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
285 
286  fJointGoalPos = fJointCurPos - TuneMinAngle;
287  nEdge = 1; // move will detect edge on maximum side
288 
289  LOGDIAG2("Joint %s (servo=%d): Move to the dark for <= %.2lf degrees.",
290  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
291 
292  moveToDark(strJointName, fJointGoalPos, TuneCalVel, byOptMask);
293 
294  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
295 
296  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
297  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
298  }
299 
300  byOptBits = getDarkOpticalLimits(m_robot.m_monitor.getJointLimitBits(),
301  byOptMask);
302 
303  //
304  // Did not find the optical limit. Do a complete spin in the opposite
305  // direction to find a limit.
306  //
307  if( byOptBits == 0x00 )
308  {
309  LOGDIAG2("Joint %s (servo=%d): Did not find the dark. Reverse direction.",
310  strJointName.c_str(), nServoId);
311 
312  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
313 
314  fJointGoalPos = fJointCurPos + M_TAU;
315  nEdge = -1; // move will detect edge on minimum side
316 
317  LOGDIAG2("Joint %s (servo=%d): "
318  "Move to the dark for <= %.2lf degrees in the maximum direction.",
319  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
320 
321  moveToDark(strJointName, fJointGoalPos, TuneCalVel, byOptMask);
322 
323  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
324 
325  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
326  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
327  }
328 
329  byOptBits = getDarkOpticalLimits(m_robot.m_monitor.getJointLimitBits(),
330  byOptMask);
331 
332  //
333  // Make sure that an optical limits has been found.
334  //
335  if( byOptBits == 0x00 )
336  {
337  LOGERROR("Joint %s (servo=%d): Did not find any optical limits.",
338  strJointName.c_str(), nServoId);
339  return -HEK_ECODE_NO_EXEC;
340  }
341 
342  //
343  // Fine tune center of optical limit.
344  //
345  for(i=0, byOptMask=0x00; i<HekOptLimitMaxPerJoint; ++i)
346  {
347  if( byOptBits & joint.m_byOptLimitMask[i] )
348  {
349  rc = fineTuneTDC(joint, joint.m_byOptLimitMask[i], nEdge, fJointCalibPos);
350  break;
351  }
352  }
353 
354  if( rc < 0 )
355  {
356  LOGERROR("Joint %s (servo=%d): Fine tuning failed.",
357  strJointName.c_str(), nServoId);
358  return rc;
359  }
360 
361  //
362  // Move to 0 degrees the shortest way.
363  //
364  if( fJointCalibPos != 0.0 )
365  {
366  if( fJointCalibPos < M_PI )
367  {
368  fJointGoalPos = fJointCalibPos;
369  }
370  else
371  {
372  fJointGoalPos = M_TAU - fJointCalibPos;
373  }
374 
375  LOGDIAG2("Joint %s (servo=%d): "
376  "Move to the new zero point at %.2lf degrees.",
377  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
378 
379  moveWait(strJointName, fJointGoalPos, TuneCalVel);
380 
381  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
382 
383  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
384  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
385  }
386 
387  //
388  // Reset odometer to new zero degree position.
389  //
390  m_robot.m_pKin->resetServoOdometer(strJointName);
391 
392  LOGDIAG2("Joint %s (servo=%d): Odometer reset.",
393  strJointName.c_str(), nServoId);
394 
395  //
396  // Adjust limits and positions to adjusted zero degree point.
397  //
398  joint.m_fMinSoftLimitRads = 0.0;
399  joint.m_fMaxSoftLimitRads = M_TAU;
400  joint.m_nMinSoftLimitOd = 0;
401  joint.m_nMaxSoftLimitOd = (int)(joint.m_fMaxSoftLimitRads *
402  joint.m_fTicksPerJointRad);
403 
404  //
405  // Finally move to the pre-defined calibration zero point position.
406  //
407  if( joint.m_fCalibPosRads != 0.0 )
408  {
409  LOGDIAG2("Joint %s (servo=%d): Move to calibrated zero pt=%.2lf degrees.",
410  strJointName.c_str(), nServoId, radToDeg(joint.m_fCalibPosRads));
411 
412  moveWait(strJointName, joint.m_fCalibPosRads, TuneCalVel);
413 
414  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
415 
416  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
417  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
418  }
419 
420  //
421  // Calibrated.
422  //
423  m_robot.m_pKin->getServoCurPosSpeed(strJointName,
424  nServoCurPos, nServoCurSpeed);
425 
426  LOGDIAG2("Joint %s (servo=%d): Calibrated by TDC optical limits:\n"
427  " Top dead center: %.02lfdeg (od=%d).",
428  strJointName.c_str(), nServoId, 0.0, nServoCurPos);
429 
430  return HEK_OK;
431 }
432 
433 int HekCalibStretch::calibrateJointByLimits(HekRobotJoint &joint)
434 {
435  // joint calibrating velocity (radians/second)
436  static double TuneCalVel = degToRad(20.0);
437 
438  // additional angle margin (radians)
439  static double TuneMarginAngle = degToRad(50.0);
440 
441  string strJointName; // joint name
442  int nServoId; // servo id
443  bool bIsReverse; // odometer is [not] reversed
444 
445  double fJointCurPos; // user's starting/current position (radians)
446  double fJointCurVel; // joint current velocity (radians/second)
447  double fJointGoalPos; // joint working goal position (radians)
448  double fJointCalibPos; // joint calibrated position (radians)
449 
450  int nServoCurPos; // servo current position (odometer ticks)
451  int nServoCurSpeed; // servo current speed (raw unitless)
452 
453  byte_t byOptMask; // optical limits mask
454  byte_t byOptBits; // optical limits bits
455  HekOpticalLimit_T *pLimit; // limit info associated with mask
456 
457  int nDir; // working direction
458  double fAngle1; // working angle 1
459  double fAngle2; // working angle 2
460 
461  int i; // working index
462  int rc = HEK_OK; // return code
463 
464  //
465  // Initialize fixed working data.
466  //
467  // Note: Only work with one optical limit per joint for now. Can extend if
468  // needed.
469  //
470  strJointName = joint.m_strName;
471  nServoId = joint.m_nMasterServoId;
472  bIsReverse = joint.m_nMasterServoDir == DYNA_DIR_CCW? false: true;
473  byOptMask = joint.m_byOptLimitMask[0];
474  pLimit = m_robot.m_monitor.getJointLimitInfo(byOptMask);
475 
476  LOGDIAG2("Joint %s (servo=%d): Calibrating by optical limits.",
477  strJointName.c_str(), joint.m_nMasterServoId);
478 
479  //
480  // Sanity check.
481  //
482  if( byOptMask == 0x00 )
483  {
484  LOGERROR("Joint %s (servo=%d): No optical limit bits set, spec correct?",
485  strJointName.c_str(), nServoId);
486  return -HEK_ECODE_INTERNAL;
487  }
488 
489  // Disable optical limit auto-stopping for the joint during calibration.
490  joint.m_bStopAtOptLimits = false;
491 
492  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
493 
494  LOGDIAG2("Joint %s (servo=%d): Starting at %.2lf uncalibrated degrees.",
495  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
496 
497  //
498  // Get current optical limit values.
499  //
500  byOptBits = getDarkOpticalLimits(m_robot.m_monitor.getJointLimitBits(),
501  byOptMask);
502 
503  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
504  // Starting in the dark.
505  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
506 
507  if( byOptBits != 0x00 )
508  {
509  LOGDIAG2("Joint %s (servo=%d): "
510  "Determining joint optical limit dark-to-light edge.",
511  strJointName.c_str(), nServoId);
512 
513  // best guess direction based on configured parked position for this joint.
514  nDir = joint.m_fParkPosRads < 0? 1: -1;
515 
516  //
517  // Calculate the maximum of the minimum travel angles to reach an edge.
518  //
519  fAngle1 = pLimit->m_fMinBlackPos - joint.m_fMinPhyLimitRads;
520  fAngle2 = joint.m_fMaxPhyLimitRads - pLimit->m_fMaxBlackPos;
521 
522  if( fAngle2 > fAngle1 )
523  {
524  fAngle1 = fAngle2;
525  }
526 
527  fAngle1 += TuneMarginAngle;
528 
529  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
530 
531  fJointGoalPos = fJointCurPos + (double)nDir * fAngle1;
532 
533  LOGDIAG2("Joint %s (servo=%d): Move to light for <= %.2lf degrees.",
534  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
535 
536  // Move to occlusion band edge.
537  multiMoveToLight(strJointName, nDir, fJointGoalPos, TuneCalVel, byOptMask);
538 
539  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
540 
541  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
542  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
543 
544  // optical bits
545  byOptBits = m_robot.m_monitor.getJointLimitBits();
546 
547  //
548  // Did't find the light, probably guessed wrong direction and joint is at
549  // a physical limit. Reverse direction.
550  //
551  if( !getLitOpticalLimits(byOptBits, byOptMask) )
552  {
553  LOGDIAG2("Joint %s (servo=%d): "
554  "Did not find the light. Reverse direction.",
555  strJointName.c_str(), nServoId);
556 
557  nDir = -nDir;
558 
559  m_robot.m_pKin->getJointCurPosVel(strJointName,
560  fJointCurPos, fJointCurVel);
561 
562  fJointGoalPos = fJointCurPos + (double)nDir * fAngle1;
563 
564  LOGDIAG2("Joint %s (servo=%d): Move to light <= %.2lf degrees.",
565  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
566 
567  multiMoveToLight(strJointName, nDir, fJointGoalPos, TuneCalVel,
568  byOptMask);
569 
570  m_robot.m_pKin->getJointCurPosVel(strJointName,
571  fJointCurPos, fJointCurVel);
572 
573  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
574  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
575  }
576 
577  // optical bits
578  byOptBits = m_robot.m_monitor.getJointLimitBits();
579 
580  //
581  // Got an edge, perform final calibration fine tuning.
582  //
583  if( getLitOpticalLimits(byOptBits, byOptMask) )
584  {
585  m_robot.m_pKin->getJointCurPosVel(strJointName,
586  fJointCurPos, fJointCurVel);
587 
588  LOGDIAG2("Joint %s (servo=%d): Got a light edge at %.2lf degrees.",
589  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
590 
591  return calibByLimitsFinal(joint, byOptMask, nDir);
592  }
593 
594  //
595  // Still no light. Give up.
596  //
597  else
598  {
599  LOGERROR("Joint %s (servo=%d): "
600  "Could not find optical limit switch light.",
601  strJointName.c_str(), nServoId);
602  return -HEK_ECODE_NO_EXEC;
603  }
604  }
605 
606 
607  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
608  // Starting in the light.
609  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
610 
611  else
612  {
613  // best guess direction
614  nDir = nServoId == HekServoIdShoulderL? -1: 1;
615 
616  //
617  // Calculate the maximum angle to reach an edge.
618  //
619  fAngle1 = pLimit->m_fMaxBlackPos - pLimit->m_fMinBlackPos;
620  fAngle1 += TuneMarginAngle;
621 
622  LOGDIAG2("Joint %s (servo=%d): "
623  "Determining joint optical limit light-to-dark minimum edge.",
624  strJointName.c_str(), nServoId);
625 
626  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
627 
628  fJointGoalPos = fJointCurPos + (double)nDir * fAngle1;
629 
630  LOGDIAG2("Joint %s (servo=%d): Move to dark for <= %.2lf degrees.",
631  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
632 
633  // Move to occlusion band edge.
634  multiMoveToDark(strJointName, nDir, fJointGoalPos, TuneCalVel, byOptMask);
635 
636  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
637 
638  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
639  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
640 
641  // optical bits
642  byOptBits = m_robot.m_monitor.getJointLimitBits();
643 
644  //
645  // Got an edge, perform final calibration fine tuning.
646  //
647  if( getDarkOpticalLimits(byOptBits, byOptMask) )
648  {
649  m_robot.m_pKin->getJointCurPosVel(strJointName,
650  fJointCurPos, fJointCurVel);
651 
652  LOGDIAG2("Joint %s (servo=%d): Got a dark edge at %.2lf degrees.",
653  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
654 
655  return calibByLimitsFinal(joint, byOptMask, nDir);
656  }
657 
658  else
659  {
660  LOGERROR("Joint %s (servo=%d): "
661  "Could not find optical limit occlusion edge.",
662  strJointName.c_str(), nServoId);
663 
664  return -HEK_ECODE_NO_EXEC;
665  }
666  }
667 
668  LOGERROR("BUG: Joint %s (servo=%d): Should not be here.",
669  strJointName.c_str(), nServoId);
670 
671  return -HEK_ECODE_INTERNAL;
672 }
673 
674 int HekCalibStretch::calibByLimitsFinal(HekRobotJoint &joint,
675  byte_t byOptMask,
676  int nDir)
677 {
678  // joint calibrating velocity (radians/second)
679  static double TuneCalVel = degToRad(20.0);
680 
681  // additional angle margin (radians)
682  static double TuneMarginAngle = degToRad(10.0);
683 
684  string strJointName; // joint name
685  int nServoId; // servo id
686  bool bIsReverse; // odometer is [not] reversed
687 
688  HekOpticalLimit_T *pLimit; // limit info associated with mask
689 
690  double fJointCurPos; // user's starting/current position (radians)
691  double fJointCurVel; // joint current velocity (radians/second)
692  double fJointGoalPos; // joint working goal position (radians)
693  double fJointCalibPos; // joint calibrated position (radians)
694 
695  int nServoCurPos; // servo current position (odometer ticks)
696  int nServoCurSpeed; // servo current speed (raw unitless)
697 
698  int i; // working index
699  int rc; // return code
700 
701  strJointName = joint.m_strName;
702  nServoId = joint.m_nMasterServoId;
703  bIsReverse = joint.m_nMasterServoDir == DYNA_DIR_CCW? false: true;
704  pLimit = m_robot.m_monitor.getJointLimitInfo(byOptMask);
705 
706  // fine tune edge
707  if( (rc = fineTuneLimit(joint, byOptMask, nDir, fJointCalibPos)) < 0 )
708  {
709  LOGERROR("Joint %s (servo=%d): Fine tuning failed.",
710  strJointName.c_str(), nServoId);
711  return rc;
712  }
713 
714  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
715 
716  fJointGoalPos = fJointCurPos - fJointCalibPos;
717 
718  //
719  // Special joint movements.
720  //
721  // Note: Slow down the elbow a tad to reduce hard stop at new zero point.
722  // Looks cleaner.
723  //
724  if( strJointName == "shoulder" )
725  {
726  string strJointName2("elbow");
727  double fGearRatios = calcJointRatios(strJointName2, strJointName);
728  m_robot.m_pKin->move(strJointName2, 0.0, 0.75 * fGearRatios * TuneCalVel);
729  }
730 
731  LOGDIAG2("Joint %s (servo=%d): Move to the new zero point at %.2lf degrees.",
732  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
733 
734  moveWait(strJointName, fJointGoalPos, TuneCalVel);
735 
736  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
737 
738  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
739  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
740 
741  m_robot.m_pKin->resetServoOdometer(strJointName);
742 
743  LOGDIAG2("Joint %s (servo=%d): Odometer reset.",
744  strJointName.c_str(), nServoId);
745 
746  //
747  // Adjust limits and positions.
748  //
749  joint.m_fMinSoftLimitRads = pLimit->m_fMinEdgePos;
750  joint.m_fMaxSoftLimitRads = pLimit->m_fMaxEdgePos;
751  joint.m_nMinSoftLimitOd = (int)(joint.m_fMinSoftLimitRads *
752  joint.m_fTicksPerJointRad);
753  joint.m_nMaxSoftLimitOd = (int)(joint.m_fMaxSoftLimitRads *
754  joint.m_fTicksPerJointRad);
755 
756  // Enable optical limit stopping for joint during normal operation.
757  joint.m_bStopAtOptLimits = true;
758 
759  LOGDIAG2("Joint %s (servo=%d): Calibrated by optical limits:\n"
760  " Physical limits: [%.2lfdeg (od=%d), %.2lfdeg (od=%d)]\n"
761  " Soft limits: [%.2lfdeg (od=%d), %.2lfdeg (od=%d)].",
762  strJointName.c_str(), nServoId,
767 
768  return HEK_OK;
769 }
770 
771 int HekCalibStretch::calibrateJointByTorqueLimits(HekRobotJoint &joint)
772 {
773  // joint calibrating velocity (radians/second)
774  static double TuneCalVel = degToRad(20.0);
775 
776  // backoff angle margin (radians)
777  static double TuneBackoff = degToRad(0.1);
778 
779  string strJointName; // joint name
780  int nServoId; // servo id
781  bool bIsReverse; // odometer is [not] reversed
782  DynaServo *pServo; // servo control
783 
784  double fMinLimit; // joint minimum position
785  double fMaxLimit; // joint maximum position
786  double fJointRange; // joint range of motion
787 
788  double fJointStartPos; // user's starting position (radians)
789  double fJointCurPos; // joint current position (radians)
790  double fJointCurVel; // joint current velocity (radians/second)
791  double fJointGoalPos; // joint working goal position (radians)
792 
793  int nServoPos; // working servo position (odometer ticks);
794 
795  strJointName = joint.m_strName;
796  nServoId = joint.m_nMasterServoId;
797  bIsReverse = joint.m_nMasterServoDir == DYNA_DIR_CCW? false: true;
798 
799  LOGDIAG2("Joint %s (servo=%d): Calibrating by torque limits.",
800  strJointName.c_str(), joint.m_nMasterServoId);
801 
802  HEK_TRY_GET_SERVO(nServoId, pServo);
803 
804  // starting position
805  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointStartPos, fJointCurVel);
806 
807  LOGDIAG2("Joint %s (servo=%d): Starting at %.2lf uncalibrated degrees.",
808  strJointName.c_str(), nServoId, radToDeg(fJointStartPos));
809 
810  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
811  // Find minimum joint limit.
812  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
813 
814  LOGDIAG2("Joint %s (servo=%d): "
815  "Determining minimum joint angle by torque limit.",
816  joint.m_strName.c_str(), nServoId);
817 
818  fJointCurPos = fJointStartPos;
819 
820  //
821  // A continuous mode servo must have its joint minimum limit within 360
822  // degreees of joint rotation from the current position. Go to this endpoint
823  // until the torque limit is found.
824  //
825  if( joint.m_bIsServoContinuous )
826  {
827  fJointGoalPos = -M_TAU + -fabs(fJointCurPos);
828  }
829 
830  //
831  // A servo mode servo has encoder endpoints that are known and finite. Go to
832  // the appropriate endpoint until the torque limit is found.
833  //
834  else
835  {
836  if( (nServoPos = pServo->CalcOdometerAtEncMin()) > 0 )
837  {
838  nServoPos = pServo->CalcOdometerAtEncMax();
839  }
840  fJointGoalPos = m_robot.m_pKin->servoPosToJointPos(strJointName, nServoPos);
841  }
842 
843  LOGDIAG2("Joint %s (servo=%d): Move to minimum >= %.2lf degrees.",
844  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
845 
846  moveToTorqueLimit(strJointName, fJointGoalPos, TuneCalVel);
847 
848  // save minimum limit position
849  m_robot.m_pKin->getJointCurPosVel(strJointName, fMinLimit, fJointCurVel);
850 
851  LOGDIAG2("Joint %s (servo=%d): Minimum reached at %.2lf degrees.",
852  strJointName.c_str(), nServoId, radToDeg(fMinLimit));
853 
854  //
855  // Off load servo by moving back to starting calibration position.
856  //
857  LOGDIAG2("Joint %s (servo=%d): Move back to start at %.2lf degrees.",
858  strJointName.c_str(), nServoId, radToDeg(fJointStartPos));
859 
860  moveWait(strJointName, fJointStartPos, TuneCalVel);
861 
862  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
863 
864  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
865  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
866 
867  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
868  // Find maximum limit.
869  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
870 
871  LOGDIAG2("Joint %s (servo=%d): "
872  "Determining maximum joint angle by torque limit.",
873  joint.m_strName.c_str(), nServoId);
874 
875  // current position
876  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
877 
878  //
879  // A continuous mode servo must have its joint minimum limit within 360
880  // degreees of joint rotation from the current position. Go to this endpoint
881  // until the torque limit is found.
882  //
883  if( joint.m_bIsServoContinuous )
884  {
885  fJointGoalPos = M_TAU + fabs(fJointCurPos);
886  }
887 
888  //
889  // A servo mode servo has encoder endpoints that are known and finite. Go to
890  // the appropriate endpoint until the torque limit is found.
891  //
892  else
893  {
894  if( (nServoPos = pServo->CalcOdometerAtEncMin()) < 0 )
895  {
896  nServoPos = pServo->CalcOdometerAtEncMax();
897  }
898  fJointGoalPos = m_robot.m_pKin->servoPosToJointPos(strJointName, nServoPos);
899  }
900 
901  LOGDIAG2("Joint %s (servo=%d): Move to maximum <= %.2lf degrees.",
902  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
903 
904  moveToTorqueLimit(strJointName, fJointGoalPos, TuneCalVel);
905 
906  // save maximum limit position
907  m_robot.m_pKin->getJointCurPosVel(strJointName, fMaxLimit, fJointCurVel);
908 
909  LOGDIAG2("Joint %s (servo=%d): Maximum reached at %.2lf degrees.",
910  strJointName.c_str(), nServoId, radToDeg(fMaxLimit));
911 
912  //
913  // Off load servo by moving back to starting calibration position
914  //
915  LOGDIAG2("Joint %s (servo=%d): Move back again to start at %.2lf degrees.",
916  strJointName.c_str(), nServoId, radToDeg(fJointStartPos));
917 
918  moveWait(strJointName, fJointStartPos, TuneCalVel);
919 
920  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
921 
922  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
923  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
924 
925  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
926  // Set zero point and limits.
927  // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
928 
929  //
930  // Joint range of motion.
931  //
932  fJointRange = fMaxLimit - fMinLimit;
933 
934  //
935  // Move to new zero point position.
936  //
937  fJointGoalPos = fMinLimit;
938 
939  LOGDIAG2("Joint %s (servo=%d): Move to the new zero point at %.2lf degrees.",
940  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
941 
942  moveToTorqueLimit(strJointName, fJointGoalPos, TuneCalVel);
943 
944  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
945 
946  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
947  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
948 
949  //
950  // Reset odometer to new zero degree position.
951  //
952  m_robot.m_pKin->resetServoOdometer(strJointName);
953 
954  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
955 
956  LOGDIAG2("Joint %s (servo=%d): Odometer reset.",
957  strJointName.c_str(), nServoId);
958 
959  //
960  // Adjust limits and positions to adjusted zero degree point.
961  //
962  joint.m_fMinPhyLimitRads = fJointCurPos;
963  joint.m_fMaxPhyLimitRads = fJointCurPos + fJointRange;
964 
965  joint.m_nMinPhyLimitOd = m_robot.m_pKin->jointPosToServoPos(
966  strJointName,
967  joint.m_fMinPhyLimitRads);
968  joint.m_nMaxPhyLimitOd = m_robot.m_pKin->jointPosToServoPos(
969  strJointName,
970  joint.m_fMaxPhyLimitRads);
971 
972  joint.m_fMinSoftLimitRads = joint.m_fMinPhyLimitRads + TuneBackoff;
973  joint.m_fMaxSoftLimitRads = joint.m_fMaxPhyLimitRads - TuneBackoff;
974 
975  // hack - should be in spec
976  if( nServoId == HekServoIdGraboid )
977  {
978  joint.m_fMaxSoftLimitRads = joint.m_fMaxPhyLimitRads - degToRad(1.0);
979  }
980 
981 
982  joint.m_nMinSoftLimitOd = m_robot.m_pKin->jointPosToServoPos(
983  strJointName,
984  joint.m_fMinSoftLimitRads);
985  joint.m_nMaxSoftLimitOd = m_robot.m_pKin->jointPosToServoPos(
986  strJointName,
987  joint.m_fMaxSoftLimitRads);
988 
989 
990  //
991  // Finally move to pre-defined calibration zero point position.
992  //
993  if( joint.m_fCalibPosRads != 0.0 )
994  {
995  LOGDIAG2("Joint %s (servo=%d): Move to calibrated zero pt=%.2lf degrees.",
996  strJointName.c_str(), nServoId, radToDeg(joint.m_fCalibPosRads));
997 
998  moveWait(strJointName, joint.m_fCalibPosRads, TuneCalVel);
999 
1000  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
1001 
1002  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
1003  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
1004  }
1005 
1006  //
1007  // Calibrated.
1008  //
1009  LOGDIAG2("Joint %s (servo=%d): Calibrated by torque:\n"
1010  " Physical limits: [%.2lfdeg (od=%d), %.2lfdeg (od=%d)]\n"
1011  " Soft limits: [%.2lfdeg (od=%d), %.2lfdeg (od=%d)].",
1012  joint.m_strName.c_str(), nServoId,
1017 
1018  return HEK_OK;
1019 }
1020 
1021 int HekCalibStretch::calibrateJointByTrust(HekRobotJoint &joint)
1022 {
1023  string strJointName; // joint name
1024  int nServoId; // servo id
1025  bool bIsReverse; // odometer is [not] reversed
1026 
1027  double fJointCurPos; // joint current position (radians)
1028  double fJointCurVel; // joint current velocity (radians/second)
1029 
1030  int nServoCurPos; // servo current position (odometer ticks)
1031  int nServoCurSpeed; // servo current speed (raw unitless)
1032 
1033  strJointName = joint.m_strName;
1034  nServoId = joint.m_nMasterServoId;
1035  bIsReverse = joint.m_nMasterServoDir == DYNA_DIR_CCW? false: true;
1036 
1037  LOGDIAG2("Joint %s (servo=%d): Calibrating by trust.",
1038  strJointName.c_str(), joint.m_nMasterServoId);
1039 
1040  //
1041  // Reset odometer to the current position
1042  //
1043  m_robot.m_pKin->resetServoOdometer(strJointName);
1044 
1045  // starting position
1046  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
1047  m_robot.m_pKin->getServoCurPosSpeed(strJointName,
1048  nServoCurPos,
1049  nServoCurSpeed);
1050 
1051  if( joint.m_eJointType == HekJointTypeContinuous )
1052  {
1053  LOGDIAG2("Joint %s (servo=%d): Calibrated by trust:\n"
1054  " Top dead center: %.2lf deg (od=%d).",
1055  strJointName.c_str(), nServoId, radToDeg(fJointCurPos), nServoCurPos);
1056  }
1057  else
1058  {
1059  LOGDIAG2("Joint %s (servo=%d): Calibrated by trust:\n"
1060  " Rotation [min, max] limits: [%.2lfdeg (od=%d), %.2lfdeg (od=%d)].",
1061  strJointName.c_str(), nServoId,
1064  }
1065 
1066  return HEK_OK;
1067 }
1068 
1069 int HekCalibStretch::fineTuneTDC(HekRobotJoint &joint,
1070  byte_t byOptMask,
1071  int nEdge,
1072  double &fPosition)
1073 {
1074  // joint calibrating velocity (radians/second)
1075  static double TuneCalVel = degToRad(10.0);
1076 
1077  // tuning slop (radians)
1078  static double TuneFudge = degToRad(5.0);
1079 
1080  string strJointName; // joint name
1081  int nServoId; // servo id
1082 
1083  byte_t byOptBits; // optical limits bits
1084  HekOpticalLimit_T *pLimit; // limit info associated with mask
1085  double fBandWidth; // occlusion band width (radians)
1086 
1087  double fJointStartPos; // user's starting position (radians)
1088  double fJointCurPos; // joint current position (radians)
1089  double fJointCurVel; // joint current velocity (radians/second)
1090  double fJointGoalPos; // joint working goal position (radians)
1091 
1092  double fLitEdgePos0; // joint lit edge position 0 (radians)
1093  double fLitEdgePos1; // joint lit edge position 1 (radians)
1094 
1095  int rc = HEK_OK; // return code
1096 
1097  strJointName = joint.m_strName;
1098  nServoId = joint.m_nMasterServoId;
1099  pLimit = m_robot.m_monitor.getJointLimitInfo(byOptMask);
1100 
1101  LOGDIAG3("Joint %s (servo=%d): opt_mask=0x%02x, edge=%d.",
1102  strJointName.c_str(), nServoId, byOptMask, nEdge);
1103 
1104  LOGDIAG2("Joint %s (servo=%d): "
1105  "Occlusion band: max=%lfdeg, center=%lfdeg, min=%lfdeg.",
1106  strJointName.c_str(), nServoId,
1107  radToDeg(pLimit->m_fMaxEdgePos),
1108  radToDeg(pLimit->m_fCenterPos),
1109  radToDeg(pLimit->m_fMinEdgePos));
1110 
1111  // width of occlusion band + some
1112  fBandWidth = pLimit->m_fMaxEdgePos - pLimit->m_fMinEdgePos + TuneFudge;
1113 
1114  // starting position
1115  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointStartPos, fJointCurVel);
1116 
1117  //
1118  // Move to the light side of one of the occlusion band edges.
1119  //
1120  fJointGoalPos = fJointStartPos + nEdge * fBandWidth;
1121 
1122  LOGDIAG2("Joint %s (servo=%d): "
1123  "Fine tune move for <= %.2lf degrees to find the first light edge.",
1124  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
1125 
1126  moveToLight(strJointName, fJointGoalPos, TuneCalVel, byOptMask);
1127 
1128  // optical bits
1129  byOptBits = m_robot.m_monitor.getJointLimitBits();
1130 
1131  //
1132  // Found edge 0.
1133  //
1134  if( getLitOpticalLimits(byOptBits, byOptMask) != 0x00 )
1135  {
1136  // mark edge 0
1137  m_robot.m_pKin->getJointCurPosVel(strJointName, fLitEdgePos0, fJointCurVel);
1138 
1139  LOGDIAG2("Joint %s (servo=%d): Edge 0 at %.2lf degrees.",
1140  strJointName.c_str(), nServoId, radToDeg(fLitEdgePos0));
1141  }
1142 
1143  //
1144  // Didn't find the light.
1145  //
1146  else
1147  {
1148  if( m_robot.m_pKin->hasOverTorqueCondition(strJointName) )
1149  {
1150  LOGERROR("Joint %s (servo=%d): Cannot calibrate: Joint is obstucted.",
1151  joint.m_strName.c_str(), nServoId);
1152  rc = -HEK_ECODE_COLLISION;
1153  }
1154  else
1155  {
1156  LOGERROR("Joint %s (servo=%d): Cannot calibrate: "
1157  "Did not find edge 0 of the occlusion band.",
1158  joint.m_strName.c_str(), nServoId);
1159  rc = -HEK_ECODE_NO_EXEC;
1160  }
1161  }
1162 
1163  //
1164  // Move in opposite direction to the light side of the other occlusion band
1165  // edge.
1166  //
1167  // Note: Even on any failures above, try to move back to a probably more
1168  // reasonable position.
1169  //
1170 
1171  // first back into the dark
1172  LOGDIAG2("Joint %s (servo=%d): "
1173  "Move back into the occlusion band at %.2lf degrees.",
1174  strJointName.c_str(), nServoId, radToDeg(fJointStartPos));
1175 
1176  moveToDark(strJointName, fJointStartPos, TuneCalVel, byOptMask);
1177 
1178  // current position in dark band
1179  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
1180 
1181  LOGDIAG2("Joint %s (servo=%d): Stopped at %.2lf degrees.",
1182  strJointName.c_str(), nServoId, radToDeg(fJointCurPos));
1183 
1184  //
1185  // Move to the opposite light side of one of the occlusion band edges.
1186  //
1187  fJointGoalPos = fJointCurPos - nEdge * fBandWidth;
1188 
1189  LOGDIAG2("Joint %s (servo=%d): "
1190  "Fine tune move for %.2lf degrees to find the opposite light edge.",
1191  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
1192 
1193  // then move to the opposite light
1194  moveToLight(strJointName, fJointGoalPos, TuneCalVel, byOptMask);
1195 
1196  // optical bits
1197  byOptBits = m_robot.m_monitor.getJointLimitBits();
1198 
1199  //
1200  // Found edge 1.
1201  //
1202  if( getLitOpticalLimits(byOptBits, byOptMask) != 0x00 )
1203  {
1204  // mark edge 1
1205  m_robot.m_pKin->getJointCurPosVel(strJointName, fLitEdgePos1, fJointCurVel);
1206 
1207  LOGDIAG2("Joint %s (servo=%d): Edge 1 at %.2lf degrees.",
1208  strJointName.c_str(), nServoId, radToDeg(fLitEdgePos1));
1209  }
1210 
1211  //
1212  // Didn't find the light edge.
1213  //
1214  else
1215  {
1216  if( m_robot.m_pKin->hasOverTorqueCondition(strJointName) )
1217  {
1218  LOGERROR("Joint %s (servo=%d): Cannot calibrate: Joint is obstucted.",
1219  joint.m_strName.c_str(), nServoId);
1220  rc = (rc == HEK_OK)? -HEK_ECODE_COLLISION: rc;
1221  }
1222  else
1223  {
1224  LOGERROR("Joint %s (servo=%d): Cannot calibrate: "
1225  "Did not find edge 1 of occlusion band.",
1226  joint.m_strName.c_str(), nServoId);
1227  rc = (rc == HEK_OK)? -HEK_ECODE_NO_EXEC: rc;
1228  }
1229  }
1230 
1231  //
1232  // Move to center of occlusion band.
1233  //
1234  if( rc == HEK_OK )
1235  {
1236  fJointGoalPos = (fLitEdgePos1 + fLitEdgePos0) / 2.0;
1237 
1238  LOGDIAG2("Joint %s (servo=%d): Move to TDC at %.2lf degrees.",
1239  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
1240 
1241  moveWait(strJointName, fJointGoalPos, TuneCalVel);
1242 
1243  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
1244 
1245  fPosition = pLimit->m_fCenterPos;
1246 
1247  LOGDIAG2("Joint %s (servo=%d): Fine tune TDC diff = %.2lf degrees.",
1248  strJointName.c_str(), nServoId, radToDeg(fPosition));
1249  }
1250 
1251  return rc;
1252 }
1253 
1254 int HekCalibStretch::fineTuneLimit(HekRobotJoint &joint,
1255  byte_t byOptMask,
1256  int nDir,
1257  double &fDeltaPos)
1258 {
1259  // joint calibrating velocity (radians/second)
1260  static double TuneCalVel = degToRad(5.0);
1261 
1262  // tuning slop (radians)
1263  static double TuneFudge = degToRad(10.0);
1264 
1265  string strJointName; // joint name
1266  int nServoId; // servo id
1267 
1268  HekOpticalLimit_T *pLimit; // limit info associated with mask
1269  byte_t byOptBits; // optical limits bits
1270 
1271  double fJointCurPos; // joint current position (radians)
1272  double fJointCurVel; // joint current velocity (radians/second)
1273  double fJointGoalPos; // joint working goal position (radians)
1274  double fLightPos; // occlusion band light position (radian)
1275  double fDarkPos; // occlusion band dark position (radian)
1276  double fEdgePos; // occlusion band edge position (radians)
1277 
1278  int rc = HEK_OK; // return code
1279 
1280  strJointName = joint.m_strName;
1281  nServoId = joint.m_nMasterServoId;
1282  pLimit = m_robot.m_monitor.getJointLimitInfo(byOptMask);
1283 
1284  m_robot.m_pKin->stop(strJointName);
1285 
1286  // optical bits
1287  byOptBits = m_robot.m_monitor.getJointLimitBits();
1288 
1289  // need to start in the light
1290  if( !getLitOpticalLimits(byOptBits, byOptMask) )
1291  {
1292  nDir = -nDir;
1293 
1294  // current position
1295  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
1296 
1297  fJointGoalPos = fJointCurPos + (double)nDir * TuneFudge;
1298 
1299  LOGDIAG2("Joint %s (servo=%d): "
1300  "Fine tune move for <= %.2lf degrees to find light edge.",
1301  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
1302 
1303  moveToLight(strJointName, fJointGoalPos, TuneCalVel, byOptMask);
1304 
1305  m_robot.m_pKin->getJointCurPosVel(strJointName, fLightPos, fJointCurVel);
1306 
1307  LOGDIAG2("Joint %s (servo=%d): Stopped at first light at %.2lf degrees.",
1308  strJointName.c_str(), nServoId, radToDeg(fLightPos));
1309  }
1310 
1311  // optical bits
1312  byOptBits = m_robot.m_monitor.getJointLimitBits();
1313 
1314  // sanity check
1315  if( getLitOpticalLimits(byOptBits, byOptMask) == 0x00 )
1316  {
1317  LOGERROR("Joint %s (servo=%d): Cannot find occlusion band edge.",
1318  strJointName.c_str(), nServoId);
1319  return -HEK_ECODE_NO_EXEC;
1320  }
1321 
1322  // move to dark
1323  nDir = -nDir;
1324 
1325  // current position
1326  m_robot.m_pKin->getJointCurPosVel(strJointName, fJointCurPos, fJointCurVel);
1327 
1328  fJointGoalPos = fJointCurPos + (double)nDir * TuneFudge;
1329 
1330  LOGDIAG2("Joint %s (servo=%d): "
1331  "Fine tune move for <= %.2lf degrees to find dark edge.",
1332  strJointName.c_str(), nServoId, radToDeg(fJointGoalPos));
1333 
1334  moveToDark(strJointName, fJointGoalPos, TuneCalVel, byOptMask);
1335 
1336  m_robot.m_pKin->getJointCurPosVel(strJointName, fDarkPos, fJointCurVel);
1337 
1338  LOGDIAG2("Joint %s (servo=%d): Stopped at first dark at %.2lf degrees.",
1339  strJointName.c_str(), nServoId, radToDeg(fDarkPos));
1340 
1341  // optical bits
1342  byOptBits = m_robot.m_monitor.getJointLimitBits();
1343 
1344  //
1345  // Didn't find the dark past the edge.
1346  //
1347  if( getDarkOpticalLimits(byOptBits, byOptMask) == 0x00 )
1348  {
1349  if( m_robot.m_pKin->hasOverTorqueCondition(strJointName) )
1350  {
1351  LOGERROR("Joint %s (servo=%d): Cannot calibrate: Joint is obstucted.",
1352  strJointName.c_str(), nServoId);
1353  rc = -HEK_ECODE_COLLISION;
1354  }
1355  else
1356  {
1357  LOGERROR("Joint %s (servo=%d): Cannot calibrate: "
1358  "Did not find the dark edge of occlusion band.",
1359  strJointName.c_str(), nServoId);
1360  rc = -HEK_ECODE_NO_EXEC;
1361  }
1362  }
1363 
1364  //
1365  // Set delta joint position as specified in spec.
1366  //
1367  fEdgePos = (fLightPos + fDarkPos) / 2.0;
1368  if( nDir > 0 )
1369  {
1370  fDeltaPos = pLimit->m_fMaxEdgePos + fDarkPos - fEdgePos;
1371  }
1372  else
1373  {
1374  fDeltaPos = pLimit->m_fMinEdgePos + fEdgePos - fDarkPos;
1375  }
1376 
1377  return rc;
1378 }
1379 
1380 double HekCalibStretch::calcJointRatios(const string &strJointName1,
1381  const string &strJointName2)
1382 {
1383  HekRobotJoint *pJoint; // working joint description
1384  double fGearRatios; // gear ratio 1 to gear ratio 2
1385 
1386  if( (pJoint = m_robot.getArmJoint(strJointName1)) != NULL )
1387  {
1388  fGearRatios = pJoint->m_fGearRatio;
1389  if( (pJoint = m_robot.getArmJoint(strJointName2)) != NULL )
1390  {
1391  fGearRatios /= pJoint->m_fGearRatio;
1392  }
1393  else
1394  {
1395  fGearRatios = 0.0;
1396  }
1397  }
1398  else
1399  {
1400  fGearRatios = 0.0;
1401  }
1402 
1403  return fGearRatios;
1404 }
1405 
1406 int HekCalibStretch::multiMoveToLight(const string &strJointName,
1407  int nDir,
1408  double fJointGoalPos,
1409  double fJointGoalVel,
1410  byte_t byMask)
1411 {
1412  string strJointName2; // helper joint name
1413  double fGearRatios; // joint 2 gear ratio to joint 1 gear ratio
1414  double fJointGoalPos2; // helper joint goal position (radians)
1415  double fJointGoalVel2; // helper joint goal velocity (radians/second)
1416 
1417  // start helper joint moving is good direction
1418  if( strJointName == "shoulder" )
1419  {
1420  strJointName2 = "elbow";
1421  fGearRatios = calcJointRatios(strJointName2, strJointName);
1422  if( nDir > 0 )
1423  {
1424  fJointGoalPos2 = m_robot.getArmJoint(strJointName2)->m_fMinSoftLimitRads;
1425  }
1426  else
1427  {
1428  fJointGoalPos2 = m_robot.getArmJoint(strJointName2)->m_fMaxSoftLimitRads;
1429  }
1430  fJointGoalVel2 = fGearRatios * fJointGoalVel;
1431  m_robot.m_pKin->move(strJointName2, fJointGoalPos2, fJointGoalVel2);
1432  }
1433 
1434  // block while joint moves to light
1435  moveToLight(strJointName, fJointGoalPos, fJointGoalVel, byMask);
1436 
1437  // stop helper joint
1438  if( strJointName2.size() > 0 )
1439  {
1440  m_robot.m_pKin->stop(strJointName2);
1441  }
1442 
1443  return HEK_OK;
1444 }
1445 
1446 int HekCalibStretch::multiMoveToDark(const string &strJointName,
1447  int nDir,
1448  double fJointGoalPos,
1449  double fJointGoalVel,
1450  byte_t byMask)
1451 {
1452  string strJointName2; // helper joint name
1453  double fGearRatios; // joint 2 gear ratio to joint 1 gear ratio
1454  double fJointGoalPos2; // helper joint goal position (radians)
1455  double fJointGoalVel2; // helper joint goal velocity (radians/second)
1456 
1457  // start helper joint moving is good direction
1458  if( strJointName == "shoulder" )
1459  {
1460  strJointName2 = "elbow";
1461  fGearRatios = calcJointRatios(strJointName2, strJointName);
1462  if( nDir > 0 )
1463  {
1464  fJointGoalPos2 = m_robot.getArmJoint(strJointName2)->m_fMinSoftLimitRads;
1465  }
1466  else
1467  {
1468  fJointGoalPos2 = m_robot.getArmJoint(strJointName2)->m_fMaxSoftLimitRads;
1469  }
1470  fJointGoalVel2 = fGearRatios * fJointGoalVel;
1471  m_robot.m_pKin->move(strJointName2, fJointGoalPos2, fJointGoalVel2);
1472  }
1473 
1474  // block while joint moves to light
1475  moveToDark(strJointName, fJointGoalPos, fJointGoalVel, byMask);
1476 
1477  // stop helper joint
1478  if( strJointName2.size() > 0 )
1479  {
1480  m_robot.m_pKin->stop(strJointName2);
1481  }
1482 
1483  return HEK_OK;
1484 }
1485 
1486 HekRobotJoint *HekCalibStretch::getHelpfulParentJoint(int nChildServoId)
1487 {
1488  switch( nChildServoId )
1489  {
1490  case HekServoIdWristPitch:
1491  return &(m_robot.m_jointsArm[HekServoIdElbow]);
1492  case HekServoIdElbow:
1493  return &(m_robot.m_jointsArm[HekServoIdShoulderL]);
1494  default:
1495  return NULL;
1496  }
1497 }
HekCalib - Hekateros calibration abstract base class interface.
int m_nMaxSoftLimitOd
joint max soft limit (odometer ticks)
Definition: hekJoint.h:169
HekOpState m_eOpState
current operational state
Definition: hekJoint.h:179
double m_fMinSoftLimitRads
joint min soft limit (radians)
Definition: hekJoint.h:166
int m_eLimitTypes
joint limit types
Definition: hekJoint.h:173
Operational robotic joint description class.
Definition: hekJoint.h:80
Optical limit switch.
Definition: hekOptical.h:148
double m_fTicksPerJointRad
encoder/odom. ticks per joint radian
Definition: hekJoint.h:157
int m_eJointType
joint type
Definition: hekJoint.h:154
HekCalibStretch - Hekateros calibration by stretching class interface.
#define M_TAU
tau = 2 * pi
Definition: hekUtils.h:67
int m_nMinPhyLimitOd
joint min phys limit (odometer ticks)
Definition: hekJoint.h:164
double radToDeg(double r)
Convert radians to degrees.
Definition: hekUtils.h:137
int m_nMasterServoId
master servo id
Definition: hekJoint.h:150
double m_fMinBlackPos
minimum complete occlusion position
Definition: hekOptical.h:179
double m_fParkPosRads
joint parked position (radians)
Definition: hekJoint.h:172
double m_fCalibPosRads
joint calibrated position (radians)
Definition: hekJoint.h:170
double m_fMinPhyLimitRads
joint min physical limit (radians)
Definition: hekJoint.h:162
Hekateros Monitor Class interface.
double m_fMaxEdgePos
maxinum edge position of occlusion band
Definition: hekOptical.h:182
bool m_bIsServoContinuous
servo should [not] be continuous mode
Definition: hekJoint.h:152
int m_nMinSoftLimitOd
joint min soft limit (odometer ticks)
Definition: hekJoint.h:168
Hekateros joint classes interfaces.
int m_nMaxPhyLimitOd
joint max phys limit (odometer ticks)
Definition: hekJoint.h:165
double m_fMaxBlackPos
maximum complete occlusion position
Definition: hekOptical.h:181
double m_fMinEdgePos
mininum edge position of occlusion band
Definition: hekOptical.h:178
byte_t m_byOptLimitMask[HekOptLimitMaxPerJoint]
optical limit mask array
Definition: hekJoint.h:175
bool m_bStopAtOptLimits
do [not] stop at optical limits
Definition: hekJoint.h:180
static byte_t getDarkOpticalLimits(byte_t byBits, byte_t byMask)
Test if any of the optical limits have been triggered (occluded).
Definition: hekOptical.h:194
HekRobot - Hekateros Robot Class interface.
#define HEK_TRY_GET_SERVO(nServoId, pServo)
Convenience macro for trying to get a servo object from dynamixel chain.
std::string m_strName
joint name
Definition: hekJoint.h:149
Top-level package include file.
double degToRad(double d)
Convert degrees to radians.
Definition: hekUtils.h:125
double m_fMaxSoftLimitRads
joint max soft limit (radians)
Definition: hekJoint.h:167
static byte_t getLitOpticalLimits(byte_t byBits, byte_t byMask)
Test if any of the optical limits are lit (not occluded).
Definition: hekOptical.h:209
static const string CalibOrder[]
Calibration order by master servo id.
int m_nMasterServoDir
master servo normalized direction
Definition: hekJoint.h:153
double m_fGearRatio
joint gear ratio
Definition: hekJoint.h:155
double m_fMaxPhyLimitRads
joint max physical limit (radians)
Definition: hekJoint.h:163
double m_fCenterPos
center of operation position
Definition: hekOptical.h:180
The <b><i>Hekateros</i></b> namespace encapsulates all <b><i>Hekateros</i></b> related constructs...
Definition: hekateros.h:56