/** 
 * \file axis.h
 * \brief axis header file
 * \details File contains structures, enumerations and functions declarations for one axis
 *
 * \author Juraj Golej
 * \version 1.0
 */


#ifndef AXIS_H_
#define AXIS_H_

#include "sam.h"
#include <inttypes.h>
#include <stdbool.h>
#include <drivers/pio/pio.h>
#include <drivers/pwm/pwm.h>
#include <drivers/timer/timer.h>
#include <utils/assert/arm_assert.h>

/**
 * \brief Get sign of number
 */
#define sign(x) (((x) > 0) - ((x) < 0))

/** 
 * \brief AXIS mode enumeration
 */
enum AXIS_MODE {
	AXIS_MODE_NONE,		///< Everything is disabled and at rest
	AXIS_MODE_MOVEFREE, ///< Free mode
	AXIS_MODE_MOVETO,	///< Move to mode
}; /* enum AXIS_MODE */

/** 
 * \brief AXIS structure
 */
struct AXIS_app {
	struct TIMER_module timer;			///< Sample timer
	struct PWM_module pwm;				///< Step PWM module
	volatile enum AXIS_MODE mode;		///< AXIS mode

	struct module {
		uint8_t stepPin;				///< Step (PWM) output pin
		uint8_t dirPin;					///< Direction output pin
		bool inverse;					///< Inverse output level on direction pin
	} module;

	struct conv {
		float constant;					///< Conversion constant from metric to frequency
		uint8_t	 microstep;				///< Actual stepper motor microstep resolution
		uint16_t rev;					///< Steps number per one rev
		uint16_t  pitch;				///< Distance traveled per one rev [mm]
	} conv;

	struct position {
		volatile int32_t absolute;		///< Absolute position
		volatile uint32_t incSteps;		///< Number of steps to the next position (useful in MOVETO mode)
		volatile uint32_t rampSteps;	///< Number of steps for deceleration (useful in MOVETO mode)
	} position;

	struct frequency {
		int32_t constant;				///< Slope constant
		volatile int32_t required;		///< Required frequency
		volatile int32_t actual;		///< Actual frequency
		volatile int32_t end;			///< End frequency
		volatile uint32_t acceleration;	///< Frequency acceleration
	} frequency;

	uint16_t maxPos;					///< Maximum move length (absolute position) [mm]
	uint16_t maxSpeed;					///< Maximum speed [mm/s]
}; /* struct AXIS_app */

/**
 * \brief Frequency regulator function
 * 
 * \param[in] module Pointer on TIMER module
 * 
 * \warning Call this function only when TIMER is disabled
 */
void AXIS_freqReg(struct TIMER_module* const module);

/**
 * \brief Check, if axis is in busy state
 * 
 * \param[in] app Pointer on AXIS app
 */
static inline bool AXIS_isBusy(struct AXIS_app* const app)
{
	if(app->mode == AXIS_MODE_NONE) {
		return false;
	}

	return true;
}

/**
 * \brief Inverse direction pin
 * 
 * \param[in] app Pointer on AXIS app
 * \param[in] inverse \c true for inverse, \false for normal operation
 */
static inline void AXIS_inverseDir(struct AXIS_app* const app, const bool inverse)
{
	Assert(app);
	app->module.inverse = inverse;
}

/**
 * \brief Calculate conversion constant
 * 
 * \param[in] app Pointer on AXIS app
 */
static inline void AXIS_calculateConvConst(struct AXIS_app* const app)
{
	Assert(app);
	Assert(app->conv.pitch);
	app->conv.constant = (float)(app->conv.rev * app->conv.microstep) / (float)app->conv.pitch;
}

/**
 * \brief Reset absolute position
 * 
 * \param[in] app Pointer on AXIS app
 */
static inline void AXIS_resetAbsolutePos(struct AXIS_app* const app)
{
	Assert(app);
	app->position.absolute = 0;
}

/**
 * \brief Get absolute position
 * 
 * \param[in] app Pointer on AXIS app
 *
 * \return \b float Absolute position in mm
 */
static inline float AXIS_getAbsPos(struct AXIS_app* const app)
{
	Assert(app);
	return (float)app->position.absolute / app->conv.constant;
}

/**
 * \brief Connect output step (PWM) pin to periphery
 * 
 * \param[in] app Pointer on AXIS app
 */
static inline void AXIS_connect(struct AXIS_app* const app)
{
	Assert(app);
	PIO_peripheral_enable(app->module.stepPin);
}

/**
 * \brief Disconnect output step (PWM) pin from periphery (directly control thought PIO controller)
 * 
 * \param[in] app Pointer on AXIS app
 */
static inline void AXIS_disconnect(struct AXIS_app* const app)
{
	Assert(app);
	PIO_peripheral_disable(app->module.stepPin);
}

/**
 * \brief Start of movement
 * 
 * \param[in] app Pointer on AXIS app
 * \param[in] mode AXIS mode
 */
static inline void AXIS_start(struct AXIS_app* const app, const enum AXIS_MODE mode)
{
	app->mode = mode;
	if(mode != AXIS_MODE_NONE) {
		app->mode = mode;
		TIMER_start(&app->timer, TIMER_MODE_REPEAT);
	}
}

/**
 * \brief Stop PWM and sample TIMER
 * 
 * \param[in] app Pointer on AXIS app
 */
static inline void AXIS_stop(struct AXIS_app* const app)
{
	app->frequency.actual = 0;
	PWM_stop(&app->pwm);
	TIMER_stop(&app->timer);
	app->mode = AXIS_MODE_NONE;
}

#endif /* AXIS_H_ */