/*  Copyright (c) 2009 by Alex Leone <acleone ~AT~ gmail.com>

    This file is part of the Arduino TLC5940 Library.

    The Arduino TLC5940 Library is free software: you can redistribute it
    and/or modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation, either version 3 of the
    License, or (at your option) any later version.

    The Arduino TLC5940 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with The Arduino TLC5940 Library.  If not, see
    <http://www.gnu.org/licenses/>. */

#ifndef TLC_SERVOS_H
#define TLC_SERVOS_H

/** \file
    TLC servo functions. */

#include "Tlc5940.h"

#ifndef SERVO_MAX_ANGLE
/** The maximum angle of the servo. */
#define SERVO_MAX_ANGLE     180
#endif
#ifndef SERVO_MIN_WIDTH
/** The 1ms pulse width for zero degrees (0 - 4095). */
#define SERVO_MIN_WIDTH     204
#endif
#ifndef SERVO_MAX_WIDTH
/** The 2ms pulse width for 180 degrees (0 - 4095). */
#define SERVO_MAX_WIDTH     410
#endif
#ifndef SERVO_TIMER1_TOP
/** The top value for XLAT and BLANK pulses.  This is with the div8 prescale,
    so
    \f$\displaystyle f_{PWM} = \frac{f_{osc}}{2 * 8 * SERVO\_TIMER1\_TOP} \f$
    The default is 20000, which corresponds to 50Hz. */
#define SERVO_TIMER1_TOP    20000
#endif
#ifndef SERVO_TIMER2_TOP
/** The top value for GSCLK pulses.  Related to SERVO_TIMER1_TOP by
    \f$\displaystyle SERVO\_TIMER2\_TOP =
        \frac{2 * 8 * SERVO\_TIMER1\_TOP}{4096} - 1 \f$
    The default is 77. */
#define SERVO_TIMER2_TOP    77
#endif

void tlc_initServos(uint8_t initAngle = 0);
void tlc_setServo(TLC_CHANNEL_TYPE channel, uint8_t angle);
uint8_t tlc_getServo(TLC_CHANNEL_TYPE channel);
uint16_t tlc_angleToVal(uint8_t angle);
uint8_t tlc_valToAngle(uint16_t value);

/** \addtogroup ExtendedFunctions
    \code #include "tlc_servos.h" \endcode
    - void tlc_initServos(uint8_t initAngle = 0) - initializes the tlc for
            servos.
    - void tlc_setServo(TLC_CHANNEL_TYPE channel, uint8_t angle) - sets a
            servo to an angle
    - uint8_t tlc_getServo(TLC_CHANNEL_TYPE channel) - gets the currently set
            servo angle */
/* @{ */

/** Initializes the tlc.
    \param initAngle the initial angle to set all servos to
            (0 - SERVO_MAX_ANGLE). */
void tlc_initServos(uint8_t initAngle)
{
    Tlc.init(tlc_angleToVal(initAngle));
#if defined(__AVR__)
    TCCR1B &= ~(_BV(CS12) | _BV(CS11) | _BV(CS10)); // stop timer1
    ICR1 = SERVO_TIMER1_TOP;
    TCNT1 = 0;
#ifdef TLC_ATMEGA_8_H
    uint8_t oldTCCR2 = TCCR2;
    TCCR2 = 0;
    TCNT2 = 0;
    OCR2 = SERVO_TIMER2_TOP / 2;
    TCCR2 = oldTCCR2;
#elif defined(TLC_TIMER3_GSCLK)
    // TODO: timer3 implementation needed...
    // pull requests would be most welcome! ;-)
#else
    uint8_t oldTCCR2B = TCCR2B;
    TCCR2B = 0;
    TCNT2 = 0;
    OCR2A = SERVO_TIMER2_TOP;
    TCCR2B = oldTCCR2B;
#endif
    TCCR1B |= _BV(CS11); // start timer1 with div 8 prescale

#elif defined(__arm__) && defined(TEENSYDUINO)
    //clear_XLAT_interrupt();
    uint32_t sc __attribute__ ((unused)) = FTM1_SC;
    FTM1_SC = 0;  // stop timer
    CMT_MSC = 0;
    CMT_CGH1 = TLC_TIMER_TEENSY3_SERVO_CGH1;
    CMT_CGL1 = TLC_TIMER_TEENSY3_SERVO_CGL1;
    CMT_MSC = 0x01; // GSCLK target is 204800 Hz
    // TODO: this reconfiguration of FTM1 crashes... why?
    // pull requests would be most welcome! ;-)
    FTM1_CNT = 0;
    FTM1_MOD = TLC_TIMER_TEENSY3_SERVO_MOD;
    FTM1_C0V = TLC_TIMER_TEENSY3_SERVO_MOD - TLC_TIMER_TEENSY3_SERVO_CV;
    FTM1_C1V = TLC_TIMER_TEENSY3_SERVO_MOD - TLC_TIMER_TEENSY3_SERVO_CV - 1;
    FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_CPWMS | TLC_TIMER_TEENSY3_SERVO_PS;
#endif
}

/** Sets a servo on channel to angle.
    \param channel which channel to set
    \param angle (0 - SERVO_MAX_ANGLE) */
void tlc_setServo(TLC_CHANNEL_TYPE channel, uint8_t angle)
{
    Tlc.set(channel, tlc_angleToVal(angle));
}

/** Gets the current angle that channel is set to.
    \param channel which channel to get */
uint8_t tlc_getServo(TLC_CHANNEL_TYPE channel)
{
    return tlc_valToAngle(Tlc.get(channel));
}

/** Converts and angle (0 - SERVO_MAX_ANGLE) to the inverted tlc channel value
    (4095 - 0). */
uint16_t tlc_angleToVal(uint8_t angle)
{
    return 4095 - SERVO_MIN_WIDTH - (
            ((uint16_t)(angle) * (uint16_t)(SERVO_MAX_WIDTH - SERVO_MIN_WIDTH))
            / SERVO_MAX_ANGLE);
}

/** Converts an inverted tlc channel value (4095 - 0) into an angle (0 -
    SERVO_MAX_ANGLE). */
uint8_t tlc_valToAngle(uint16_t value)
{
    return SERVO_MAX_ANGLE * (4095 - SERVO_MIN_WIDTH - value)
            / (SERVO_MAX_WIDTH - SERVO_MIN_WIDTH);
}

/* @} */

#endif

