/* stepper.cpp -- Code for schip's interrupt driver stepper motor.
**
**  Stepper motor driver driven from tick interrupt.
**  Hardcodes stepper motor attached to pins 2-5 of UNO type board.
**
**  Uses schip's kernel core scheduler() with SCHIPTCB defined
**/

// Get all the system and local definitions
#include "stepint.h"

/****************** interrupt driven stepper control ***************/
// select one step operation mode -- only HALF step works for Grubber...
//#define FULLSTEP
#define HALFSTEP

// define to use 1ms 50% PWM on long steps and holds
#define PWMSTEPS 15	// tick count threshold for PWM on output

//#define STEPTRACE // use LED to show when motor is running

// mask for stepper motor control pins -- on UNO Port D
//#define STEP1PIN	2	// Port D, Bit 2 - mask 0x04
#define STEP1MASK	0x04
//#define STEP2PIN	3	// Port D, Bit 3 - mask 0x08
#define STEP2MASK	0x08
//#define STEP3PIN	4	// Port D, Bit 4 - mask 0x10
#define STEP3MASK	0x10
//#define STEP4PIN	5	// Port D, Bit 5 - mask 0x20
#define STEP4MASK	0x20

// mask for all the stepper bits on Port D
#define STEPBITMASK	(STEP1MASK | STEP2MASK | STEP3MASK | STEP4MASK)

// global control values:
int stepsDone = 0;	// number of steps executed, negative == bkd (dir=0)
byte step2do = 0;	// steps to execute -- 0 = none, 255 = forever
byte tick2do = 0;	// final number of timer ticks between steps
byte nticks = 0;	// current number of timer ticks between steps
byte tickcnt = 0;	// number of timer ticks executed -- decr to 0
byte stepstate = 0;	// state bits, see below for values
#define sDIR  0x01	// bottom bit selects direction
#define sHLD  0x02	// if set, then leave step pins ON at end, else all OFF
#define sPRF  0x04	// if set, then profile motor startup
#define sRUN  0x80	// motor running when set

char stepnum = 0;	// motor step number in sequence -- 0-3 or 0-7

/** initialize stepper motor pins
 **/
void initStepper(void)
{
	// set pins stepper pins to OUTPUTs
	pinMode( STEP1PIN, OUTPUT );
	pinMode( STEP2PIN, OUTPUT );
	pinMode( STEP3PIN, OUTPUT );
	pinMode( STEP4PIN, OUTPUT );
	// just in case make sure they are all off
	digitalWrite( STEP1PIN, 0 );
	digitalWrite( STEP2PIN, 0 );
	digitalWrite( STEP3PIN, 0 );
	digitalWrite( STEP4PIN, 0 );

	return;
}

/** Start the stepper control
 **  @param dir   -- direction 1,0 arbitrary YMMV
 **  @param steps -- number of steps to do 0=stop, 255=forever
 **  @param ticks -- 1ms timer ticks between steps
 **  @param prf   -- number of motor startup profile ticks
 ** 					(for low tick counts only)
 **  @param hold  -- 0= shutoff coils,
 **					  else hold state of all coil-pins at end
 **
 **   note: the step count does not account for FULL/HALF setting
 **			so should be doubled if using HALF-steps
 **/
void startStepper( byte dir, byte steps, byte ticks, byte prf, byte hold )
{
	byte xstate = ( sRUN |
				  (dir  ? sDIR : 0) |
				  (prf  ? sPRF : 0) |  // not actually used, but...
				  (hold ? sHLD : 0) );
	ticks -= 1; // note: account for post decrement when running...
	//todo: doesn't deal right with high tick2do counts (vis wraparound)...
	byte xticks = ( prf ? (ticks + prf+1) : ticks );
	
	// shut off interrupts to set values used by tick interrupt
	byte oldSREG = SREG;	// save interrupt settings
	cli();
	stepstate = xstate;
	step2do = steps;
	tick2do = ticks;
	nticks = xticks;
	tickcnt = 0;	// forces first step on first call below
	SREG = oldSREG; // restore interrupt settings

	return;
}

/** set a new Step Tick count (speed) for running motor
**/
void setStepSpeed( byte ticks )
{
	tick2do = ticks;
}

/** set a new Step Direction for running motor
**/
void setStepDir( byte dir )
{
	if( dir )
		stepstate |= sDIR;
	else
		stepstate &= ~sDIR;
}

/** Returns the number of steps left to do, or 0 when done
 **/
byte getSteps2do(void)
{
	// single byte access, so no interrupt mangling needed
	return step2do;
}

/** Returns the number of steps done so far.
 ** Note that the counter is explicitly reset by the user
 **  so this is the number of steps since the last setStepsDone() call
 **  with the sign indicating the direction.
 **  Signed value, where - is nominal backwards
 **/
int getStepsDone(void)
{
	int rval;

	// need interrupt mangling to access int value
	byte oldSREG = SREG;	// save interrupt settings
	cli();
	rval = stepsDone;
	SREG = oldSREG; // restore interrupt settings

	return rval;
}

/** Sets the number of steps done so far
 **  Signed value, where negative is nominal backwards
 **  Probably just used to zero counter at predictable position.
 **/
void setStepsDone(int n)
{
	// need interrupt mangling to access int value
	byte oldSREG = SREG;	// save interrupt settings
	cli();
	stepsDone = n;
	SREG = oldSREG; // restore interrupt settings

	return;
}

/** stop operation immediately
 **  clears steps2do but leaves stepsdone intact
 **  @param hold  -- 0= shutoff coils, else hold state of all coil-pins at end
 **/
void stopStepper( byte hold )
{
	// todo: definately need interrupt disable around this...
	byte oldSREG = SREG;	// save interrupt settings
	cli();
	stepstate = 0;			// clear RUN
	step2do = 0;
	SREG = oldSREG; // restore settings

	if( hold == 0 )
	{
		PORTD &= ~STEPBITMASK;	// clear coils
	}
	else
	{
		stepstate = sHLD;	// flag HOLD setting
		tickcnt = 3;		// force entry to if( tickcnt != 0 ) below
	}

	return;
}

/** Function called from kernel tick interrupt in wiring.c w/ SCHIPTCB set
 **  manages counters and stepper motor steps.
 **/
void schipTick()
{
	static byte portval;	// motor control port current bits

	if( !(stepstate & (sRUN|sHLD)) )
	{
		// note, at minimum sRUN will be set when running
		// if not running or holding, don't do anything
		return;
	}
	else if( (tickcnt != 0) )
	{
		// not ready for a step yet
		--tickcnt;

#ifdef PWMSTEPS
		// implement PWM on long step periods
		if( ((nticks - tickcnt) > PWMSTEPS) ||	// Running with long period
			((stepstate & (sRUN|sHLD)) == sHLD) )	// Holding while stopped
		{
			// alternate all bits ON/OFF until next step
			if( tickcnt & 0x01 )
				PORTD |= portval;
			else
				PORTD &= ~STEPBITMASK;

			if( ((stepstate & (sRUN|sHLD)) == sHLD) &&
				(tickcnt == 1) )
			{
				// Holding while stopped,
				//  force toggle tickcnt
				// note: accounting for decrement...
				//  we toggle between 2 and 3 on each entry
				tickcnt = 3;
			}
		}
#endif //PWMSTEPS

		return;
	}

	// if this is the last step in the run,
	//  just cleanup and reset
	if( !(step2do) )
	{
		// we waited for the last tickcnt to expire,
		//  shutoff step output bits if not HOLD
		//  and clear state so we don't do it again...
		if( stepstate & sHLD )
		{
			// insure that the bits are left ON
			//  only needed when doing PWM ... but big deal...
			PORTD |= portval;
		}
		else
		{
			//  shut off coils,
			//  otherwise we will leave coils charged
			PORTD &= ~STEPBITMASK;
		}

#ifdef PWMSTEPS
		stepstate &= sHLD;		// clear RUN but preserve HOLD setting
		if( stepstate & sHLD )	// if we want PWM in HOLD
			tickcnt = 3;		// force entry to if( tickcnt != 0 ) above
#else //not PWMSTEPS
		stepstate = 0;			// clear everything and let it sit
#endif //not PWMSTEPS

#ifdef STEPTRACE
		// turn LED OFF on UNO
		PORTB &= ~(1<<PINB5);
#endif // STEPTRACE

		return;
	}

	// ...OK now we can do a real step...

#ifdef STEPTRACE
	// toggle LED ON on UNO
	PORTB ^= (1<<PINB5);
#endif // STEPTRACE

	// shut all bits OFF (briefly)
	PORTD &= ~STEPBITMASK;

#ifdef FULLSTEP
	// set the appropriate bits ON for full single-coil stepping
	switch( stepnum )
	{
	  case 0:
		PORTD |= STEP1MASK;
	  break;

	  case 1:
		PORTD |= STEP2MASK;
	  break;

	  case 2:
		PORTD |= STEP3MASK;
	  break;

	  case 3:
		PORTD |= STEP4MASK;
	  break;

	  default:
	  	stepnum = 0;
	  break;
	}

	// recycle at end
	if( stepstate & sDIR )
	{
		++stepsDone;
		if( ++stepnum >= 4 )		stepnum = 0;
	}
	else
	{
		--stepsDone;
		if( --stepnum < 0 )		stepnum = 3;
	}
		
#endif // FULLSTEP

#ifdef HALFSTEP
	// set the appropriate bits ON for half stepping
	switch( stepnum )
	{
	  case 0:
		PORTD |= STEP1MASK;
	  break;

	  case 1:
		PORTD |= (STEP1MASK | STEP2MASK);
	  break;

	  case 2:
		PORTD |= STEP2MASK;
	  break;

	  case 3:
		PORTD |= (STEP2MASK | STEP3MASK);
	  break;

	  case 4:
		PORTD |= STEP3MASK;
	  break;

	  case 5:
		PORTD |= (STEP3MASK | STEP4MASK);
	  break;

	  case 6:
		PORTD |= STEP4MASK;
	  break;

	  case 7:
		PORTD |= (STEP4MASK | STEP1MASK);
	  break;

	  default:
	  	stepnum = 0;
	  break;
	}

	// save ON bits
	portval = PORTD & STEPBITMASK;

	// increment step to do and recycle at end
	if( stepstate & sDIR )
	{
		if( ++stepnum >= 8 )
		{
			stepnum = 0;
		}
	}
	else
	{
		if( --stepnum < 0 )
		{
			stepnum = 7;
		}
	}

#endif // HALFSTEP

	// profile: ramp up motor 'speed' on each step during startup 
	if( nticks > tick2do )	--nticks;

	tickcnt = nticks;		// restart tick counter

	if( step2do != 255 )	// keep track of steps done
		--step2do;

	return;
}
/****************** end'o interrupt driven stepper control ***************/
