// codppmx8inv - ATtiny85 timing using timer1 interrupt

// Started: Feb 27, 2015  G. D. (Joe) Young <jyoung@islandnet.com>
//
// Revised: Mar/Apr/15 - see codppmx7 for earlier version(s)
//          Sep 13/15 - codppmx8 adds callsign identification to all modes. From condev2
//          Sep 16/15 - set callspeed to 40 wpm, same format all modes, detect no callsign
//          Jan 11/16 - invert Tx output for FET keying of Tx
//          Feb 27/16 - fix LED-on error, add invert possibility for LED as well as for Tx
//          Aug 15/16 - callsign at end of transmission codppmx8US
//          Aug 28/16 - correct offset interval calcn for trailing callsign
//                    - reconfig trailing calsign logic
//          Aug 29/16 - add no-overlap adjustment, fixup call insertion all modes
//          Aug 31/16 - fixup call suppression option
//
// To permit re-programming the callsign without otherwise modifying the controller
// code, the fox ID (MOE..MO5) and the call are stored in processor's eeprom at 
// location 0, in the form of morse codewords (see condev2).

// An example eeprom entry:
// >>> dump eeprom 
// 0000  07 0f 02 00 0b 18 3f 1d  1b 1d ff 5e ff ff ff ff  |.... .?....^....|
// which corresponds to the text: MOE WB0QYQ followed by call terminator FF and by
// call length in dots 0x5e ==> 94.

// The codeword storage format is used to avoid storing the table of ASCII-to-morse
// An arduino utility, text2code, can be used to produce an intel-hex file for loading
// into eeprom.



// There are 4 modes of operation selected by analogue value on pin 7:
//  fmode = 0  Sprint slow
//          1  Sprint fast
//          2  continuous MO
//          3  normal 1min/4min timing



#include <avr/io.h>
#include <avr/interrupt.h>
#include <EEPROM.h>

//#define TPOLARITY_POS      // Tx output active, positive true
//#define LPOLARITY_POS      // LED output active, positive true
#define TPOLARITY_OD       // Tx output open-drain negative true
#define LPOLARITY_OD       // LED output open-drain negative true

#define CALL_INTERVAL 600  // time in seconds between sending callsign
//#define THISFOX 1   // now lookup fox number from eeprom

//#define NO_OVERLAP    // to shorten ON time by one fox ID

// interrupt service variables
volatile word ovflcnt = 0;
word loopovfl;

const byte ledPin = 0;
const byte txPin = 1;
const byte ainPin = A1;

byte fmode, thisfox;
word onTime, offTime, ledtime, calltime;
boolean code = false;              // true during code sending
boolean sendm, sendo, msent, sendid, sendsp, cend, ledon;
boolean sendlsp, sendwsp, eltOn;   // loop control of morse output
unsigned long timer;
unsigned long dotLen = 221;        // dot length in millis( ) ticks (1200/wpm)1.8432
//unsigned long cdotLen = 55;  // dot length while sending call sign
unsigned long cdotLen = 111;  // dot length while sending call sign 20 wpm US
unsigned long savedotLen;

// change interrupt service to compare match interrupt
ISR( TIM1_COMPA_vect ) {
  ovflcnt += 1;
}

void setupTimer1( ) {
  TCCR1 = bit(CTC1) | 0x0e; // do not use pwm mode
  OCR1C = 224;    // 225 div with 1.8432 MHz xtal
  OCR1A = 224;    // using compare match int
  TIMSK |= bit(OCIE1A);  //add timer1 overflow interrupt
} // setupTimer1 for interrupt generation on overflow

// new functions and variables for callsign insertion
void setupNext( byte );       // functions to setup pointers to code bytes
void setupCall( );
byte setGetBit( );    // code byte parsing, now returns fence bit position 7..0
bool getBit( );
byte codeChar, nextelt, nextBit, bitCnt, nextChar, thisLength;
byte callend;
bool endCode, callsend, firstcall;
word calldurn, callintctr;



void setup( ) {

  int temp = analogRead( ainPin );      // get operating mode
  fmode = temp>>8;

  onTime = 12;
  offTime = 48;
  if( fmode == 1 ) dotLen -= 64;        // fast sprint
  savedotLen = dotLen;
  if( fmode == 3 ) {
    onTime = 60;
    offTime = 240;
  } // if normal timing
  noInterrupts( );    // attempt to sync millis( ) with timer1
  setupTimer1( );
  interrupts( );
#ifdef LPOLARITY_OD
  pinMode( ledPin, INPUT );  // setup for open drain o/p
  digitalWrite( ledPin, LOW ); // will setup no pullups, 0 in reg.
#endif
#ifdef LPOLARITY_POS
  digitalWrite( ledPin, LOW );  // setup for active high o/p
  pinMode( ledPin, OUTPUT );
#endif
#ifdef TPOLARITY_OD
  pinMode( txPin, INPUT );    // setup for open drain o/p, Tx ON pin pulls low
  digitalWrite( txPin, LOW );
#endif
#ifdef TPOLARITY_POS
  digitalWrite( txPin, LOW );
  pinMode( txPin, OUTPUT );     //active high for Tx ON with inverted version
#endif
  eltOn = false;
  cend = false;
  ledon = true;

  codeChar = EEPROM.read( 2 );  //determine fox number from EE code character
  thisfox = setGetBit( );
  callend = 4;                  //locate end of callsign
  while( EEPROM.read( callend ) != 0xff ) callend++;

  thisLength = 3;
  if( fmode == 2 ) {
    thisfox = 0;              // no fox id for continuous
    thisLength = 2;
    ledtime = ovflcnt + 30;   // 30 sec for led 
  }

// Setup timers
  if( fmode == 0 || fmode == 1 ) {
    loopovfl = ovflcnt+1+(thisfox-1)*onTime;  //indexed start for sprint
  } else {
    loopovfl = ovflcnt + 1;    // no indexing for normal
  } // if sprint modes

//  calltime = ovflcnt+2;
  calltime = ovflcnt+1;
  callsend = false;
  firstcall = true;
// calculate rounded offset for trailing call insertion in seconds
  if( callend > 4 ) {         // allow for suppression of call sign
    calldurn = ( EEPROM.read( callend+1 )*60 + 500 )/1000;
  } else {
    calldurn = 0;
  }
  callintctr = CALL_INTERVAL+onTime+offTime;

#ifdef NO_OVERLAP       //adjust timing to prevent overlap due to ID completion
  if( fmode == 0 || fmode == 3 ) {
    word adjsec = ( ( 26+(thisfox<<1) )*120 + 500 )/1000;
    onTime -= adjsec;
    offTime += adjsec;
  }
  if( fmode == 1 ) {
    word adjsec = ( ( 26+(thisfox<<1) )*87 + 500 )/1000;
    onTime -= adjsec;
    offTime += adjsec;
  }
#endif

} // setup( )


void loop( ) {

  if( !code && loopovfl == ovflcnt ) {  // start sending
    loopovfl += onTime;
    dotLen = savedotLen;
    nextChar = 0;
    setupNext( nextChar );      // get first letter of fox code
    code = true;
    cend = false;
    calltime = ovflcnt;       // set call time test to start of ontime
    callsend = false;
    callintctr = callintctr-onTime-offTime;
  }

  if( code && loopovfl == ovflcnt && fmode != 2 ) {    // end sending
    loopovfl += offTime;
    cend = true;    // delay end until id complete
  }

  if( fmode==2 && ledtime == ovflcnt ) ledon = false;
  
  if( (ovflcnt - calltime) >= (onTime - calldurn) && fmode == 3 ) {
    callsend = true;
  }
  if( code && firstcall && fmode <= 1 && (ovflcnt-calltime) >= (onTime-calldurn) ) {
    firstcall = false;
    callsend = true;
  }
  if( !firstcall && fmode <= 1 && (ovflcnt - calltime) >= (callintctr + (onTime-calldurn)) ) {
    callintctr = CALL_INTERVAL;
    callsend = true;
  }
  if( fmode == 2 && (ovflcnt - calltime) >= CALL_INTERVAL ) {
    calltime = ovflcnt;
    callsend = true;
  }

  
  if( code && !eltOn ) {
    timer = millis( ) + dotLen;            // setup for dot length
    if( getBit( ) ) timer += 2*dotLen;      // sending a dash
#ifdef TPOLARITY_OD
    pinMode( txPin, OUTPUT );
#endif
#ifdef TPOLARITY_POS
    digitalWrite( txPin, HIGH );           //active high version
#endif
#ifdef LPOLARITY_OD
    if( ledon ) pinMode( ledPin, OUTPUT );
#endif
#ifdef LPOLARITY_POS
    if( ledon ) digitalWrite( ledPin, HIGH );
#endif
    eltOn = true;
  } // if not sending, start first element

  if( code && eltOn && !sendsp && ( timer < millis( ) ) ) {
#ifdef TPOLARITY_OD
    pinMode( txPin, INPUT );
#endif
#ifdef TPOLARITY_POS
    digitalWrite( txPin, LOW );           //active high version
#endif
#ifdef LPOLARITY_OD
    pinMode( ledPin, INPUT );     //Feb 17/16
#endif
#ifdef LPOLARITY_POS
    digitalWrite( ledPin, LOW );  //Feb 17/16
#endif
    timer += dotLen;    // timing space following element
    sendsp = true;
    if( endCode ) {
      endCode = false;
      sendlsp = true;
      timer += 2*dotLen;
      nextChar++;
      if( (nextChar == thisLength) || (nextChar == callend) ) {
        dotLen = savedotLen;
        sendwsp = true;
        timer += 4*dotLen;
        nextChar = 0;          // reset pointer to characters
      } // if last character of this group was just sent
      setupNext( nextChar );
      if( codeChar == 0 ) {
        sendwsp = true;
        timer += 4*dotLen;
        nextChar++;
        setupNext( nextChar );    // if space charcter to be sent
      }
    } // extend space to letter space or word space
  } // if element on time is up

  if( code && sendsp && ( timer < millis( ) ) ) {
    eltOn = false;
    sendsp = false;
    if( sendlsp ) {
      sendlsp = false;
    } // if letter space just ended
    if( sendwsp ) {
      sendwsp = false;
      if( cend ) {
        code = false;  // turn off sending
        ledon = false;   // and led after first burst
      } // if last word space, end sending
//      if( fmode == 2 && callsend ) {
//      if( fmode != 3 && callsend ) {
      if( callsend ) {
        callsend = false;
        setupCall( );
      } // if time to send a callsign
    } // if word space ended, setup to begin again
  } // if sending space 
  
} // loop( )


// support function implementations

void setupNext( byte nextChar ) {
  codeChar = EEPROM.read( nextChar );
  setGetBit( );
} // setup to first code byte (usually for M)

void setupCall( ) {
  nextChar =  4;    // start of callsign in EE
  codeChar = EEPROM.read( nextChar );
  dotLen = cdotLen;
  if( codeChar == 0xff ) {              // allow for suppressing call
    nextChar = 0;
    codeChar = EEPROM.read( nextChar );
    dotLen = savedotLen;
  }
  setGetBit( );
} // setup to first code byte of callsign ID

byte setGetBit( ) {
  bitCnt = 7;
  nextBit = 0b10000000;
  while( (bitCnt > 0) && ((nextBit & codeChar) == 0) ) {
    nextBit = nextBit>>1;
    bitCnt--;
  } // while leading zeros before fence bit
  return bitCnt;
} // setup the code word parsing

bool getBit( ) {
  nextBit = nextBit>>1;
  bitCnt--;
  if( bitCnt == 0 ) {
    endCode = true;
  } // if last bit has just been selected
  return( (nextBit & codeChar) == nextBit );
} // get next code element to send


