/* SCHervo.h - Interrupt driven Servo library for Arduino using 16 bit timers.
   For UNO and PRO-mini using ATmega328, Timer 1 is coopted by this library.
   Any output capable pin can be assigned to control a servo.
   See the SCHervo class definition for brief description of methods.

  rewanked by schip from Version 2 in Arduino 1.0.4
  original: Copyright (c) 2009 Michael Margolis.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/* 
  A servo is activated by creating an instance of the SCHervo class passing
  the desired pin to the attach() method. The servos are pulsed in the
  background using the value most recently written using the write() method

  Note that analogWrite of PWM on pins associated with the timer are
   disabled when the first servo is attached.
  Timers are seized as needed in groups of 8 servos - not 12 as in original.
  The sequence used to sieze timers is defined in timers.h

  The interface is:
   SCHervo - Class for manipulating servo motors connected to Arduino pins.

 */

#ifndef SCHervo_h
#define SCHervo_h

// get basic types
#include <Arduino.h>

// Get the prototypes for the schip-scheduler
//   used by the "Move" interface Task
#include "schip_scheduler.h"

#define Servo_VERSION           4      // software version of this library

// position extremes and starting point, in nominal degrees
// the extremes are nominal as some servos may move further
//  when a servo is attached it should specify min,max to set actual pulse values
//  if the servo moves more than 180 these numbers are greater than 1 degree steps
#define MAXCW     0		// absolute maximums
#define MAXCCW  180
#define MIDPOS ((MAXCCW-MAXCW)/2)	// nominal mid-point

// NOTE: all below are in microseconds
// original limit values, a little too narrow
//#define MIN_PULSE_WIDTH       544     // the shortest pulse sent to a servo
//#define MAX_PULSE_WIDTH      2400     // the longest pulse sent to a servo
// the nominal spec is 1000->2000 uSec range but motors don't seem to agree...
// schip determined these max limits with the TowerPRO MG995 >180deg
#define MIN_PULSE_WIDTH       500     // the shortest pulse sent to a servo
#define MAX_PULSE_WIDTH      2700     // the longest pulse sent to a servo
#define DEFAULT_PULSE_WIDTH  1500     // default mid-setting pw when servo is attached
#define REFRESH_INTERVAL    20000     // minimum time to refresh servos in microsec

// flag indicating an invalid servo index --
//  (not) returned from attach(): too many servos
#define INVALID_SERVO         255

// ServoMove control structure for internal use
//  but has to be here to get into SCHervo class
//  so ServoUpdateTask can find it...
//   NOTE: cur_us and step_us to 8x fixed point...
//	  cur_us ranges from ~ 500->2700, so 8x is 4000->21600 (well within 16bit range)
//     ~30 deg change in uS is around 350, and 350 20ms steps is max 7sec travel time
//	   so... 8x is 56sec max travel time
//	 also degree endposition and curpostion are kinda redundant,
//	  but I'm keeping them
typedef struct
{
	uint8_t curposition;		// current position in degrees, set on each refresh step
	uint8_t endposition;		// desired end position in degrees
	uint16_t end_us;			// desired end position in microseconds
	uint16_t cur_us;			// current position in microseconds
	int16_t step_us;			// incr number of microsec per move step (- is reverse...)
	uint16_t steps;				// number of steps to move cur->end (0 when done)
	uint8_t state;				// Motor running state
} servoMove;

// the real interface
class SCHervo
{
public:
  SCHervo();
  uint8_t attach(uint8_t pin);  // attach the given pin to the next free channel, 
  							 	//  sets pinMode,
							 	//  returns channel number or 0 if failure
  uint8_t attach(uint8_t pin, uint16_t min, uint16_t max); // as above but also sets
  								//  min and max microsec pulse values for writes. 
   								//  default min is 500, max is 2700
  void setmm(uint16_t min, uint16_t max); // reset min and max pulse widths
  void detach();				// disconnect from pin
  uint16_t clampNmap(uint16_t value); //  map an angle value
								//  into microseconds for setting motor pulse
  void write(uint16_t value);   // if value is < MIN_PULSE_WIDTH it's treated as an angle,
  								//  otherwise as pulse width in microseconds 
  	  	  	  	  	  	  		//  NOTE: clamped to 0-180 degrees
								//   and the MIN/MAX microsec set for the servo
  void writeMicroseconds(uint16_t value); // Write pulse width in microseconds
  	  	  	  	  	  	  	    //  deprecated, for internal use,
								//  instead use write( bignumber )
  void turnOff();				// power off servo but keep device active
  uint16_t read();          	// returns current pulse width as an angle
  								//  between 0 and 180 degrees
  uint16_t readMicroseconds();  // returns current pulse width in microseconds
  								//  NOTE: was read_us() in first release
  bool attached();          	// return true if this servo is attached,
  								//  otherwise false 
  // schip added more controlled interface, requries schip_scheduler library
  void startMove( uint8_t newpos, uint16_t time, uint8_t off ); // start controlled motion
  uint16_t stopMove(); 		// stop controlled motion, return current degree position
  uint8_t ready();			// check if controlled motion is done
  servoMove sMove;			// controlled motion data
  	  	  	  	  	  	  	//  accessed by ServoUpdateTask() so has to be public...

private:
   void taskinit(uint8_t pos);	// init servoMove and set initial position in degrees
   void setcurpos(uint8_t pos);	// util to set servoMove current position in degrees
   uint8_t servoIndex;      	// index into the channel data for this servo
   int8_t min;              	// minimum pulse width -- this value times 4
   								//  added to MIN_PULSE_WIDTH    
   int8_t max;              	// maximum pulse width -- this value times 4
   								//  added to MAX_PULSE_WIDTH   
};

#endif // SCHervo_h
