/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
  Part of the Wiring project - http://wiring.org.co

  Copyright (c) 2004-08 Hernando Barragan
  Based on pulse.c by Pascal Stang - Copyright (C) 2000-2002

  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., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

// The pulseOut command will work on PWM output pins 3, 4, 5

#include "WProgram.h"

extern "C" {
  extern void timerAttach(uint8_t, void(*userFunc)(void));
  extern void timerDetach(uint8_t);
  extern void timer1PWMOff(void);
  extern void timer1Init(void);
  extern uint16_t timer1GetPrescaler(void);
  extern uint8_t * portspin[];
}



void pulseT1Init(void);
void pulseT1Off(void);
void pulseT1ASetFreq(uint16_t);
void pulseT1BSetFreq(uint16_t);
void pulseT1CSetFreq(uint16_t);
void pulseT1ARun(uint16_t);
void pulseT1BRun(uint16_t);
void pulseT1CRun(uint16_t);
void pulseT1AStop(void);
void pulseT1BStop(void);
void pulseT1CStop(void);
uint16_t pulseT1ARemaining(void);
uint16_t pulseT1BRemaining(void);
uint16_t pulseT1CRemaining(void);
void pulseT1AService(void);
void pulseT1BService(void);
void pulseT1CService(void);
void beginPulse(void);
void pulseFrequency(uint8_t, uint16_t);
void pulseRun(uint8_t, uint16_t);
void pulseRun(uint8_t);
void pulseOut(uint8_t pin, uint16_t freq);
void pulseOut(uint8_t, uint16_t, uint16_t);


extern short __attribute__ ((progmem)) timerPrescaleFactor[];
// pulse generation registers
volatile static unsigned char  pulseT1AMode;
volatile static unsigned short pulseT1ACount;
volatile static unsigned short pulseT1APeriodTics;
volatile static unsigned char  pulseT1BMode;
volatile static unsigned short pulseT1BCount;
volatile static unsigned short pulseT1BPeriodTics;
volatile static unsigned char  pulseT1CMode;
volatile static unsigned short pulseT1CCount;
volatile static unsigned short pulseT1CPeriodTics;
void pulseT1AService(void);
void pulseT1BService(void);
void pulseT1CService(void);

// pulse mode bit definitions
// PULSE_MODE_COUNTED
// if true, the requested number of pulses are output, then output is turned off
// if false, pulses are output continuously
#define PULSE_MODE_CONTINUOUS		0x00
#define PULSE_MODE_COUNTED		0x01

// functions

volatile uint8_t pulseFlags = 0x00;



/*************************************************************
 * Pulse Input
 *************************************************************/

/* Measures the length (in microseconds) of a pulse on the pin
 * by Mellis from Arduino for core API compatibility
 * State is HIGH or LOW, the type of pulse to measure
 * Works on pulses from 10 microseconds to 3 minutes in length
 * Must be called at least N microseconds before the start of the pulse
 */
/*
unsigned long pulseIn(uint8_t pin, uint8_t state)
{
  // cache the port and bit of the pin in order to speed up the
  // pulse width measuring loop and achieve finer resolution.  calling
  // digitalWrite() instead yields much coarser resolution.
  //int r = port_to_input[digitalPinToPort(pin)];
  //int bit = digitalPinToBit(pin);
  int r = _SFR_IO_ADDR(*portspin[pin/8]);
  int bit = pin % 8;
  int mask = 1 << bit;
  unsigned long width = 0;

  // compute the desired bit pattern for the port reading (e.g. set or
  // clear the bit corresponding to the pin being read).  the !!state
  // ensures that the function treats any non-zero value of state as HIGH.
  state = (!!state) << bit;

  // wait for the pulse to start
  while ((_SFR_IO8(r) & mask) != state)
    ;
	
  // wait for the pulse to stop
  while ((_SFR_IO8(r) & mask) == state)
    width++;
	
  // convert the reading to microseconds.  the slower the CPU speed, the
  // proportionally fewer iterations of the loop will occur (e.g. a 
  // 4 MHz clock will yield a width that is one-fourth of that read with
  // a 16 MHz clock).  each loop was empirically determined to take
  // approximately 23/20 of a microsecond with a 16 MHz clock.
  return width * (16000000UL / F_CPU) * 20 / 23;
}
*/
unsigned long pulseIn(uint8_t pin, uint8_t state) {
  pulseIn(pin, state, 1000000UL);
}

unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
{
  // cache the port and bit of the pin in order to speed up the
  // pulse width measuring loop and achieve finer resolution.  calling
  // digitalWrite() instead yields much coarser resolution.
  //int r = port_to_input[digitalPinToPort(pin)];
  //int bit = digitalPinToBit(pin);
  int r = _SFR_IO_ADDR(*portspin[pin/8]);
  int bit = pin % 8;
  int mask = 1 << bit;
  unsigned long width = 0;

  unsigned long numloops = 0;
  unsigned long maxloops = (timeout*(F_CPU/1000000L))/16;


  // compute the desired bit pattern for the port reading (e.g. set or
  // clear the bit corresponding to the pin being read).  the !!state
  // ensures that the function treats any non-zero value of state as HIGH.
  state = (!!state) << bit;

  // wait for the pulse to start
  while ((_SFR_IO8(r) & mask) != state)
    if (numloops++ == maxloops)
      return 0;

  // wait for the pulse to stop
  while ((_SFR_IO8(r) & mask) == state)
    width++;

  // convert the reading to microseconds.  the slower the CPU speed, the
  // proportionally fewer iterations of the loop will occur (e.g. a
  // 4 MHz clock will yield a width that is one-fourth of that read with
  // a 16 MHz clock).  each loop was empirically determined to take
  // approximately 23/20 of a microsecond with a 16 MHz clock.
  return width * (16000000UL / F_CPU) * 20 / 23;
}


void pulseOut(uint8_t pin, uint16_t freq) {
  pulseOut(pin, freq, 0);
}


void pulseOut(uint8_t pin, uint16_t freq, uint16_t nPulses) {
  //if(bit_is_clear(pulseFlags, 6)) {
  //pulseT1Init();
  //  pulseFlags |= _BV(6);
  //}
  beginPulse();
  pulseFrequency(pin, freq);
  pulseRun(pin, nPulses);  
}


inline void beginPulse() {
  if(bit_is_clear(pulseFlags, 6)) {  
    pulseT1Init();
    pulseFlags |= _BV(6);
  }
}


inline void endPulse() {
  pulseT1Off();
}


void pulseFrequency(uint8_t channel, uint16_t freq) {
  if(channel == 5) 
    pulseT1ASetFreq(freq);
  if(channel == 4)
    pulseT1BSetFreq(freq);
  if(channel == 3)
    pulseT1CSetFreq(freq);
}


inline void pulseRun(uint8_t channel) {
  pulseRun(channel, 0);
}


void pulseRun(uint8_t channel, uint16_t nPulses) {
  if(channel == 5)
    pulseT1ARun(nPulses);
  if(channel == 4)
    pulseT1BRun(nPulses);
  if(channel == 3)
    pulseT1CRun(nPulses);
}


void pulseStop(uint8_t channel) {
  if(channel == 5)
    pulseT1AStop();
  if(channel == 4)
    pulseT1BStop();
  if(channel == 3)
    pulseT1CStop();
}


/*boolean pulseRunning(int channel) {
  if(channel == 5)
    return (pulseT1ARemaining() > 0 ? true : false);
  if(channel == 4)
    return (pulseT1BRemaining() > 0 ? true : false);
  if(channel == 3)
    return (pulseT1CRemaining() > 0 ? true : false); 
}*/


uint16_t pulseRunning(uint8_t channel) {
  if(channel == 5)
    return pulseT1ARemaining();
  if(channel == 4)
    return pulseT1BRemaining();
  if(channel == 3)
    return pulseT1CRemaining();
}


void pulseT1Init(void) {
  timer1Init();

  // try to make sure that timer1 is in "normal" mode
  // most importantly, turn off PWM mode
  timer1PWMOff();

  pulseT1AMode = 0;
  pulseT1BMode = 0;
  pulseT1CMode = 0;

  pulseT1ACount = 0;
  pulseT1BCount = 0;
  pulseT1CCount = 0;
  pulseT1APeriodTics = 0x8000;
  pulseT1BPeriodTics = 0x8000;
  pulseT1CPeriodTics = 0x8000;

  timerAttach(TIMER1OUTCOMPAREA_INT, pulseT1AService);
  timerAttach(TIMER1OUTCOMPAREB_INT, pulseT1BService);
  timerAttach(TIMER1OUTCOMPAREC_INT, pulseT1CService);
}


void pulseT1Off(void) {
  // turns pulse outputs off immediately
	
  pulseT1ACount = 0;
  pulseT1BCount = 0;
  pulseT1CCount = 0;
  // disconnect OutputCompare action from OC1A pin
  TCCR1A &= ~_BV(COM1A1);
  TCCR1A &= ~_BV(COM1A0);

  // disconnect OutputCompare action from OC1B pin
  TCCR1A &= ~_BV(COM1B1);
  TCCR1A &= ~_BV(COM1B0);

  TCCR1A &= ~_BV(COM1C1);
  TCCR1A &= ~_BV(COM1C0);

  timerDetach(TIMER1OUTCOMPAREA_INT);
  timerDetach(TIMER1OUTCOMPAREB_INT);
  timerDetach(TIMER1OUTCOMPAREC_INT);
}


void pulseT1ASetFreq(uint16_t freqHz) {
  // get the current prescaler setting
  //uint16_t prescaleDiv = 1<<(pgm_read_byte(timerPrescaleFactor+(TCCR1B & TIMER_PRESCALE_MASK)));
  // calculate how many tics in period/2
  // this is the (timer tic rate)/(2*requested freq)
  pulseT1APeriodTics = ((uint32_t)CPU_FREQ/((uint32_t)timer1GetPrescaler()*2*freqHz));
}


void pulseT1BSetFreq(uint16_t freqHz) {
  //uint16_t prescaleDiv = 1<<(pgm_read_byte(timerPrescaleFactor+(TCCR1B & TIMER_PRESCALE_MASK)));
  pulseT1BPeriodTics = ((uint32_t)CPU_FREQ/((uint32_t)timer1GetPrescaler()*2*freqHz));
}


void pulseT1CSetFreq(uint16_t freqHz) {
  //uint16_t prescaleDiv = 1<<(pgm_read_byte(timerPrescaleFactor+(TCCR1B & TIMER_PRESCALE_MASK)));
  pulseT1CPeriodTics = ((uint32_t)CPU_FREQ/((uint32_t)timer1GetPrescaler()*2*freqHz));
}


void pulseT1ARun(uint16_t nPulses) {
  if(nPulses) {
    // if the nPulses is non-zero, use "counted" mode
    pulseT1AMode |= PULSE_MODE_COUNTED;
    pulseT1ACount = nPulses<<1;
  }
  else {
    // if nPulses is zero, run forever
    pulseT1AMode &= ~PULSE_MODE_COUNTED;
    pulseT1ACount = 1<<1;
  }
  // set OutputCompare action to toggle OC1A pin
  TCCR1A &= ~_BV(COM1A1);
  TCCR1A |= _BV(COM1A0);

  //OCR1A = TCNT1 + pulseT1APeriodTics;
  OCR1A += pulseT1APeriodTics;

  // enable OutputCompare interrupt
  TIMSK |= _BV(OCIE1A);
}


void pulseT1BRun(uint16_t nPulses) {
  if(nPulses) {
    pulseT1BMode |= PULSE_MODE_COUNTED;
    pulseT1BCount = nPulses<<1;
  }
  else {
    pulseT1BMode &= ~PULSE_MODE_COUNTED;
    pulseT1BCount = 1<<1;
  }
  TCCR1A &= ~_BV(COM1B1);
  TCCR1A |= _BV(COM1B0);
  //OCR1B = TCNT1 + pulseT1BPeriodTics;

  OCR1B += pulseT1BPeriodTics;

  TIMSK |= _BV(OCIE1B);
}


void pulseT1CRun(uint16_t nPulses) {
  if(nPulses) {
    pulseT1CMode |= PULSE_MODE_COUNTED;
    pulseT1CCount = nPulses<<1;
  }
  else {
    pulseT1CMode &= ~PULSE_MODE_COUNTED;
    pulseT1CCount = 1<<1;
  }
  TCCR1A &= ~_BV(COM1C1);
  TCCR1A |= _BV(COM1C0);


  OCR1C += pulseT1CPeriodTics;

  ETIMSK |= _BV(OCIE1C);
}


void pulseT1AStop(void) {
  // stop output regardless of remaining pulses or mode
  // go to "counted" mode
  pulseT1AMode |= PULSE_MODE_COUNTED;
  pulseT1ACount = 0;
}


void pulseT1BStop(void) {
  pulseT1BMode |= PULSE_MODE_COUNTED;
  pulseT1BCount = 0;
}


void pulseT1CStop(void) {
  pulseT1CMode |= PULSE_MODE_COUNTED;
  pulseT1CCount = 0;
}


uint16_t pulseT1ARemaining(void) {
  // return the number of pulses remaining for channel A
  // add 1 to make sure we round up, >>1 equivalent to /2
  return (pulseT1ACount+1)>>1;
}


uint16_t pulseT1BRemaining(void) {
  return (pulseT1BCount+1)>>1;
}


uint16_t pulseT1CRemaining(void) {
  return (pulseT1CCount+1)>>1;
}


void pulseT1AService(void) {
  if(pulseT1ACount) {
    OCR1A += pulseT1APeriodTics;
				
    if(pulseT1AMode & PULSE_MODE_COUNTED)
      pulseT1ACount--;
  }
  else {
    // pulse count has reached zero
    // disable the output compare's action on OC1A pin
    TCCR1A &= ~_BV(COM1A1);
    TCCR1A &= ~_BV(COM1A0);
    // disable the output compare's interrupt to stop pulsing
    TIMSK &= ~_BV(OCIE1A);
  }
}


void pulseT1BService(void) {
  if(pulseT1BCount) {
    OCR1B += pulseT1BPeriodTics;

    if(pulseT1BMode & PULSE_MODE_COUNTED)
      pulseT1BCount--;
  }
  else {
    TCCR1A &= ~_BV(COM1B1);
    TCCR1A &= ~_BV(COM1B0);
    TIMSK &= ~_BV(OCIE1B);
  }
}


void pulseT1CService(void) {
  if(pulseT1CCount) {
    OCR1C += pulseT1CPeriodTics;

    if(pulseT1CMode & PULSE_MODE_COUNTED)
      pulseT1CCount--;
  }
  else {
    TCCR1A &= ~_BV(COM1C1);
    TCCR1A &= ~_BV(COM1C0);
    ETIMSK &= ~_BV(OCIE1C);
  }
}


