/*******************************************************************************
* Copyright © 2018 TRINAMIC Motion Control GmbH & Co. KG
* (now owned by Analog Devices Inc.),
*
* Copyright © 2023 Analog Devices Inc. All Rights Reserved. This software is
* proprietary & confidential to Analog Devices, Inc. and its licensors.
*******************************************************************************/

/*
 *  This is a basic proof-of-concept implementation of a linear motion ramp
 *  generator. It is designed to run with 1 calculation / ms.
 *
 *
 */
#include "LinearRamp.h"

void tmc_linearRamp_init(TMC_LinearRamp *linearRamp)
{
	linearRamp->maxVelocity     = 0;
	linearRamp->targetPosition  = 0;
	linearRamp->targetVelocity  = 0;
	linearRamp->rampVelocity    = 0;
	linearRamp->acceleration    = 0;
	linearRamp->encoderSteps	= u16_MAX;
	linearRamp->lastdVRest      = 0;
	linearRamp->lastdXRest      = 0;
	linearRamp->rampEnabled     = false;
}

void tmc_linearRamp_computeRampVelocity(TMC_LinearRamp *linearRamp)
{
	if (linearRamp->rampEnabled)
	{
		// update target velocity according actual set acceleration
		// (scaling pre-factor of 1000 used for 1ms velocity ramp handling)

		int32_t dV = linearRamp->acceleration;

		// to ensure that small velocity changes at high set acceleration are also possible
		int32_t maxDTV = abs(linearRamp->targetVelocity - linearRamp->rampVelocity);
		if (maxDTV < (dV/1000))
			dV = maxDTV*1000;

		dV += linearRamp->lastdVRest;
		linearRamp->lastdVRest = dV % 1000;

		if (linearRamp->rampVelocity < linearRamp->targetVelocity)
		{
			// accelerate motor
			linearRamp->rampVelocity += dV/1000;	// divide with pre-factor
		}
		else if (linearRamp->rampVelocity > linearRamp->targetVelocity)
		{
			// decelerate motor
			linearRamp->rampVelocity -= dV/1000;	// divide with pre-factor
		}
	}
	else
	{
		// use target velocity directly
		linearRamp->rampVelocity = linearRamp->targetVelocity;
	}

	// limit ramp velocity
	linearRamp->rampVelocity = tmc_limitInt(linearRamp->rampVelocity, -linearRamp->maxVelocity, linearRamp->maxVelocity);
}

void tmc_linearRamp_computeRampPosition(TMC_LinearRamp *linearRamp)
{
	if (linearRamp->rampEnabled)
	{
		// update target position according actual set acceleration and max velocity
		// (scaling pre-factor of 1000 used for 1ms position ramp handling)

		// limit position difference for further computations
		int32_t targetPositionsDifference = linearRamp->targetPosition-linearRamp->rampPosition;

		// limit the sqrti value in case of high position differences
		int64_t sqrtiValue = tmc_limitS64(((int64_t)120 * (int64_t)linearRamp->acceleration * (int64_t)(abs(targetPositionsDifference))) / (int64_t)linearRamp->encoderSteps, 0, (int64_t)linearRamp->maxVelocity*(int64_t)linearRamp->maxVelocity);

		// compute max allowed ramp velocity to ramp down to target
		int32_t maxRampStop = tmc_sqrti(sqrtiValue);

		// compute max allowed ramp velocity
		int32_t maxRampTargetVelocity = 0;
		if (targetPositionsDifference > 0)
		{
			maxRampTargetVelocity = tmc_limitInt(maxRampStop, 0, (int32_t)linearRamp->maxVelocity);
		}
		else if (targetPositionsDifference < 0)
		{
			maxRampTargetVelocity = tmc_limitInt(-maxRampStop, -(int32_t)linearRamp->maxVelocity, 0);
		}
		else
		{
			//maxRampTargetVelocity = 0;
		}

		int32_t dV = linearRamp->acceleration;  // pre-factor ~ 1/1000

		// to ensure that small velocity changes at high set acceleration are also possible
		int32_t maxDTV = abs(maxRampTargetVelocity - linearRamp->rampVelocity);
		if (maxDTV < (dV / 1000))
			dV = maxDTV * 1000;

		dV += linearRamp->lastdVRest;
		linearRamp->lastdVRest = dV % 1000;

		// do velocity ramping
		if (maxRampTargetVelocity > linearRamp->rampVelocity)
		{
			linearRamp->rampVelocity += dV / 1000;
		}
		else if (maxRampTargetVelocity < linearRamp->rampVelocity)
		{
			linearRamp->rampVelocity -= dV / 1000;
		}

		// limit positionRampTargetVelocity to maxRampTargetVelocity
		//linearRamp->rampVelocity = tmc_limitInt(linearRamp->rampVelocity, -abs(maxRampTargetVelocity), abs(maxRampTargetVelocity));

		// do position ramping using actual ramp velocity to update dX
		int64_t dX = ((int64_t)linearRamp->rampVelocity * (int64_t)linearRamp->encoderSteps) / ((int64_t)60) + linearRamp->lastdXRest;

		// scale actual target position
		int64_t tempActualTargetPosition = (int64_t)linearRamp->rampPosition * 1000;

		// reset helper variables if ramp position reached target position
		if (abs(linearRamp->targetPosition - linearRamp->rampPosition) <= 10) /*abs(dX)*/
		{
			// sync ramp position with target position on small deviations
			linearRamp->rampPosition = linearRamp->targetPosition;

			// update actual target position
			tempActualTargetPosition = (int64_t)linearRamp->rampPosition * 1000;

			dX = 0;
			linearRamp->lastdXRest = 0;
			linearRamp->rampVelocity = 0;
		}
		else
		{
			// update actual target position
			tempActualTargetPosition += dX;
		}

		int64_t absTempActualTargetPosition = (tempActualTargetPosition >= 0) ? tempActualTargetPosition : -tempActualTargetPosition;

		if (tempActualTargetPosition >= 0)
			linearRamp->lastdXRest = (absTempActualTargetPosition % 1000);
		else if (tempActualTargetPosition < 0)
			linearRamp->lastdXRest = -(absTempActualTargetPosition % 1000);

		// scale actual target position back
		linearRamp->rampPosition = tempActualTargetPosition / 1000;
	}
	else
	{
		// use target position directly
		linearRamp->rampPosition = linearRamp->targetPosition;

		// hold ramp velocity in reset
		linearRamp->rampVelocity = 0;
	}
}
