/* morse.c flick the morse code bit up and down to send messages. Uses TIMER3.
 *  Copyright (C) 2022 by Jed Marti
 *   This program 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.
 *
 *  This program 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 this program.  If not, see https://www.gnu.org/licenses/.
 */

#include <C8051F380.h>
#include <stdlib.h>
#include <stdint.h>

#include "lfxmit.h"
#include "storage.h"
#include "serial.h"
#include "config.h"
#include "morse.h"
#include "lex.h"
#include "strings.h"


// For the carrier command.
extern __xdata char    STR[];
extern __xdata uint8_t ttype;


// ASCII characters taken from the ARRL "Operating Nanual for Radio Amateurs",
// page 4.4. Each character is followed by the pattern terminated with a 0.
// See morse.h for values.
const uint8_t Morse[] = {
  ' ', 0,
  'A', dit, dah, 0,
  'B', dah, dit, dit, dit, 0,
  'C', dah, dit, dah, dit, 0,
  'D', dah, dit, dit, 0,
  'E', dit, 0,
  'F', dit, dit, dah, dit, 0,
  'G', dah, dit, dit, 0,
  'H', dit, dit, dit, dit, 0,
  'I', dit, dit, 0,
  'J', dit, dah, dah, dah, 0,
  'K', dah, dit, dah, 0,
  'L', dit, dah, dit, dit, 0,
  'M', dah, dah, 0,
  'N', dah, dit, 0,
  'O', dah, dah, dah, 0,
  'P', dit, dah, dah, dit, 0,
  'Q', dah, dit, dah, dah, 0,
  'R', dit, dah, dit, 0,
  'S', dit, dit, dit, 0,
  'T', dah, 0,
  'U', dit, dit, dah, 0,
  'V', dit, dit, dit, dah, 0,
  'W', dit, dah, dah, 0,
  'X', dah, dit, dit, dah, 0,
  'Y', dah, dit, dah, dah, 0,
  'Z', dah, dah, dit, dit, 0,
  '0', dah, dah, dah, dah, dah, 0,
  '1', dit, dah, dah, dah, dah, 0,
  '2', dit, dit, dah, dah, dah, 0,
  '3', dit, dit, dit, dah, dah, 0,
  '4', dit, dit, dit, dit, dah, 0,
  '5', dit, dit, dit, dit, dit, 0,
  '6', dah, dit, dit, dit, dit, 0,
  '7', dah, dah, dit, dit, dit, 0,
  '8', dah, dah, dah, dit, dit, 0,
  '9', dah, dah, dah, dah, dit, 0,

  '@', dit, dah, dah, dit, dah, dit, 0,
  '.', dit, dah, dit, dah, dit, dah, 0,
  ',', dah, dah, dit, dit, dah, dah, 0,
  '?', dit, dit, dah, dah, dit, dit, 0,
  '-', dah, dit, dit, dit, dit, dah, 0,
  '=', dah, dit, dit, dit, dah, 0,
  ':', dah, dah, dah, dit, dit, dit, 0,
  ';', dah, dit, dah, dit, dah, dit, 0,
  '(', dah, dit, dah, dah, dit, 0,
  ')', dah, dit, dah, dah, dit, dah, 0,
  '/', dah, dit, dit, dah, dit, 0,
  '"', dit, dit, dit, dah, dit, dit, dah, 0,
  '\'', dit, dah, dah, dah, dah, dit, 0,
  '_', dit, dit, dah, dah, dit, dah, 0,
  '+', dit, dah, dit, dah, dit, 0,
  0xFF
};


// Set when a dit time goes by.
volatile __bit ditbit;

// Number of milliseconds per dit.
__xdata uint32_t ditMilliseconds;

// The interrupt counter.
volatile __xdata uint32_t ditCounter;

// The space counter is the number of dits to pass between characters.
__xdata uint8_t spaceDits;

// Random stuff from elsewhere.
extern __bit              errorThrown;
extern __bit              doit;
extern __xdata floatOrInt IACC;
extern uint16_t           DREG;
extern __xdata uint8_t    D[];


void ISS_TIMER3(void) __interrupt 14
// Interrupt on once per millisecond. Used to control timing on Morse code.
{
  uint8_t osfr;
  osfr = SFRPAGE;
  SFRPAGE = 0;
  if (ditCounter > ditMilliseconds)
  {
    ditCounter = 0;
    ditbit = 1;
  }
  else ++ditCounter;
  TMR3CN &= ~0xC0;
  SFRPAGE = osfr;
}



void initMorse(void)
// Use timer 3 with SYSCLK / 12 (4 megahertz) base rate. We set the divisor to
// 4000 which gives us millisecond timing for each dit.
{
  ditCounter = 0;
  spaceDits = DEFAULTSPACEDITS;
  ditMilliseconds = DEFAULTDITMS;
  TMR3 = TMR3RL = RELOAD;
  TMR3CN = 0x04;   // No low int, not split, run on, sysclk / 12.
}



#define state IACC.i8.b8a

static uint8_t __code *findChar(uint8_t ch)
// Find the character in the Morse code table and return the address of the
// dits/dahs for that character. Converts character to upper case.
{
  //  uint8_t state = 0;
  uint8_t __code *pt;

  // Convert to upper case.
  state = 0;
  if (ch >= 'a' && ch <= 'z') ch -= 32;
  for (pt = Morse; ; ++pt)
  {
    switch(state) {
    case 0:
      if (*pt == ch)
      {
        pt++;
        return pt;
      }
      else if (*pt == 0xFF) return NULL;
      state = 1;
      break;

    case 1:
      if (*pt == 0) state = 0;
      break;
    }
  }
}

#undef state


static void sendDit(void)
// Send a dit. Turn on the Morse bit and LED3. Enable timer interrupts, then
// wait for the bit time, turn off the interrupt, LED3, and the Morse bit.
{
  MORSE = 1;
  LED3 = 1;
  ditbit = 0;
  ditCounter = 0;
  TMR3 = RELOAD;
  EIE1 |= ET3;
  while (!ditbit);
  EIE1 &= ~ET3;
  LED3 = 0;
  MORSE = 0;
}


static  void sendDitSpace(void)
// Just wait for a dit bit time. No LED3, no Morse bit.
{
  ditbit = 0;
  ditCounter = 0;
  TMR3 = RELOAD;
  EIE1 |= ET3;
  while (!ditbit);
  EIE1 &= ~ET3;
}



static void sendDah(void)
// Send a dah - 3 dits times worth as above.
{
  MORSE = 1;
  LED3 = 1;
  ditbit = 0;
  ditCounter = 0;
  TMR3 = RELOAD;
  EIE1 |= ET3;
  while (!ditbit);
  ditbit = 0;
  while (!ditbit);
  ditbit = 0;
  while (!ditbit);
  EIE1 &= ~ET3;
  LED3 = 0;
  MORSE = 0;
}


static void sendSpace(void)
// This is the spacing between characters. Wait for the number of space dits,
// that can be set.
{
  uint8_t i;
  ditbit = 0;
  ditCounter = 0;
  TMR3 = RELOAD;
  EIE1 |= ET3;
  for (i = 0; i < spaceDits; ++i)
  {
    while (!ditbit);
    ditbit = 0;
  }
  EIE1 &= ~ET3;
}


void sendChar(uint8_t ch)
// Send a single character. Display bad characters, but keep going.
{
  uint8_t const *mrs;


  // Look the character up.
  mrs = findChar(ch);
  if (mrs == NULL)
  {
    putstring("Can't send character '");
    putch(ch);
    putstring("'\n");
    return;
  }

  for(; *mrs != 0; ++mrs)
  {
    if (*mrs == dit) sendDit();
    else sendDah();
    sendDitSpace();
  }

  sendSpace();
}



void sendString(char *s)
// Send a string.
{
  while (*s != 0) sendChar(*s++);
}



void waitMS(uint8_t n)
/*     Wait for some number of milliseconds using timer 3. Note that we have to save and
 *   restore the old counter. Active waiting, does not sleep.
 * Parameters:
 *   n: The number of millisecond interrupts to wait. 1-255.
 * External References:
 *   ditMilliseconds: The number of milliseconds to wait (saved, changed, then restored).
 *   ditCounter: Interrupt counter.
 *   ditbit: Set to 1 when timer expires.
 * Returned values: none
 * Errors detected: none
 */
{
  uint32_t olddit;
  olddit = ditMilliseconds;
  ditMilliseconds = n;
  ditbit = 0;
  ditCounter = 0;
  TMR3 = RELOAD;
  EIE1 |= ET3;
  while (!ditbit);
  EIE1 &= ~ET3;
  ditMilliseconds = olddit;
}
  


void runSend(void)
// This is the top level command. The format is:
//     SEND <expression>;
// and the expression needs to be a string.
{
  incPC();
  parseExpression();
  if (errorThrown || !doit) return;
  
  DREG = popST();
  if (tag() == DESC_STRING)
  {
    putstring("Sending ");
    putstring((char*) &D[DREG + 3]);
    terpri();
    sendString((char*) &D[DREG + 3]);
    return;
  }
  else
  {
    putstring("SEND not a string\n");
    errorThrown = 1;
    return;
  }
}



void runCarrier(void)
// Turn the carrier on/off mostly for testing. Command line only.
//     CARRIER [ON | OFF]
{
  ntok();
  if (ttype == LEXID)
  {
    if (strCaseEq("on"))
    {
      MORSE = 1;
      LED3 = 1;
    }
    else if (strCaseEq("off"))
    {
      MORSE = 0;
      LED3 = 0;
    }
    else
    {
      putstring("CARRIER ");
      putstring(STR);
      putstring("?\n");
    }
  }
  else
    putstring("CARRIER ON|OFF\n");
}
      
      
  
    



// EOF
