/* 
  wdbw.c
  sib@mbari.org
  12-nov-2003 created (sib)
  21-jan-2004 updated (sib)

  WatchDog BurnWire firmware for mapping AUV.

  A timer counts down from x seconds, and if it reaches 0, a line is set
  high to turn on a relay to burn a burnwire that drops a weight. After y seconds,
  the relay turns off to prevent running down the batteries and ground faulting.

*/

const char *VERSION_STRING = ("\xd\xa" __DATE__ " WDBW.C V1.0\xd\xa");

#include <pic.h>

#ifdef PORTBIT
#error PORTBIT already defined
#endif

#define	PORTBIT(adr, bit)	((unsigned)(&adr)*8+(bit))

static volatile bit ACommsDropNow_in @ PORTBIT(PORTA, 2); // RA2, pin 1
static volatile bit WDC_in           @ PORTBIT(PORTB, 0); // RB0, pin 7 (INT)
static volatile bit WDStatus_out     @ PORTBIT(PORTA, 1); // RA1, pin 18
static volatile bit DropWeight_out   @ PORTBIT(PORTA, 0); // RA0, pin 17
static volatile bit UART_Tx_out      @ PORTBIT(PORTB, 5); // RB5, pin 12
static volatile bit UART_Rx_in       @ PORTBIT(PORTB, 2); // RB2, pin 9

/*
  on startup: if it is a power-on reset, then reinitialize all timers and start counting
              else continue counting or asserting output without reinitialization.
  when timer expires: assert burnwire output, and remember state. set another
              n minute timer to burn wire.
*/

#define WD_TIMEOUT_DEFAULT (15*60) /* watchdog timeout in seconds */
#define BW_TIMEOUT_DEFAULT (15*60) /* burnwire timeout in seconds */

#define CMD_MAX_CHARS (10) /* max number of chars per incoming serial command */

void puts (const char *);

#define clear_usart_errors { \
  if (OERR) { \
    CREN = 0; CREN = 1; \
    RA4 ^= 1; /* toggle RA4, pin 3 */ \
  } \
  if (FERR) { \
    char dummy; \
    dummy = RCREG; \
    FERR = 0; \
    CREN = 0; CREN = 1; \
    RB6 ^= 1; /* toggle RB6, pin 12 */ \
  } \
 }

//writes a character to the serial port
void putch(unsigned char c)
{
  while(!TXIF) { //set when tx register is empty
    clear_usart_errors;
    CLRWDT();
  }
  TXREG=c;
}

void puts (const char *str)
{
  while (*str)
    putch(*str++);
}

unsigned char getch_noblock (void)
{
  clear_usart_errors;
  if (RCIF) { /* set when rx register is empty */
    return RCREG;
  }

  return 0; // if no character received
}

unsigned char getch (void)
{
  FERR = 0; /* clear framing error bit */
  while (!RCIF) { /* set when rx register is empty */
    clear_usart_errors;
    CLRWDT();
  }
  return RCREG;
}

/* print a (un)signed 5 digit integer in decimal format */
void putintdec (unsigned int c, char _signed)
{
  unsigned int temp;

  if (_signed && (signed int)c<0) {
    putch('-');
    c = -(signed int)c;
  }
  putch((temp = c / 10000) + '0');
  c -= temp*10000;
  putch((temp = c / 1000) + '0');
  c -= temp*1000;
  putch((temp = c / 100) + '0');
  c -= temp*100;
  putch((temp = c / 10) + '0');
  c -= temp*10;
  putch(c + '0');
}

// fills cmd with a null terminated string up to max_chars long
// cmd does not include '!' or '\r'
// returns number of chars in cmd
// returns 0 right away if there is not a complete message yet
// returns 1 for an a blank command; i.e. "!\r"
// all commands must start with a '!' and end with a '\r'
int getCmd (unsigned char *cmd, int max_chars)
{
  enum _getCmdStates {GET_EXCL=0, GET_RET=1}; // states for this command
  bank2 static unsigned char buffer[CMD_MAX_CHARS];
  bank2 static unsigned char state = GET_EXCL;
  bank2 static unsigned char *ptr = buffer;
  bank2 static unsigned char index = 0;
  unsigned char ch;

  switch (state)
    {
    case GET_EXCL: // waiting for the '!' start of packet
      ch = getch_noblock();
      if (ch != '!') // not the right starting letter or timed out
	{
	  return 0;
	}
      ptr = buffer;
      *buffer = 0; // set the first char to null
      index = 0;
      state = GET_RET;
      goto GET_RET_CODE;
      //break; // never get here

    GET_RET_CODE:
    case GET_RET:
      ch = getch_noblock();
      if (ch == 0) // timedout
	{
	  return 0;
	}
      else if (ch == '!') // oops got the start char, reset
	{
	  ptr = buffer;
	  index = 0;
	  return 0;
	}
      else if (ch == '\r') // end of command
	{
	  *ptr = 0;
	  // drop past switch statement now
	}
      else // normal char
	{
	  *ptr++ = ch; // store the character and increment the ptr
	  if (++index >= CMD_MAX_CHARS) //overflow
	    { // drop chars and go back to EXCL state
	      state = GET_EXCL;
	    }
	  return 0;
	}
      break;
    }

  state = GET_EXCL; // reset state
  
  if (index == CMD_MAX_CHARS) // overflow of buffer
    {
      return 0;
    }
  
  // got an entire command. copy it over to cmd buffer
  for (index=0, ptr=buffer; index < max_chars; ++index, ++ptr)
    {
      *cmd++ = *ptr;
      if (!*ptr) // end of string
	return (index+1);
    }
  
  return 0; //must have blown past max_chars
}

volatile unsigned int wd_timer; /* watchdog timer in seconds */
volatile unsigned int bw_timer; /* burnwire timer in seconds */
volatile unsigned int time; /* 1 second timer, will overflow every 65536 seconds */
volatile char count; // cascade counter for timer2 int

void interrupt isr(void)
{
  if (TMR2IF) { // Timer2
    TMR2IF = 0;
    if (++count >= 20) { // 200 * 20 should be a 1 second period
      count = 0;
      time++; // will overflow every 65536 seconds
      if (wd_timer) {
	wd_timer--;
      }
      if (bw_timer) {
	bw_timer--;
      }
    }
  } // if (TMR2IF)
}
      
void main (void)
{
  unsigned char cmd[CMD_MAX_CHARS]; // for serial input
  unsigned char num_chars_in; // for serial input
  unsigned int time_old; // for 1 second timer
  unsigned char PORb, BORb, TOb; // buffers for the reset bits

  PORb = POR; POR = 1;
  BORb = BOR; BOR = 1;
  TOb = TO; TO = 1;

  time_old = time = 0; // intialize 1 second clock

  /* set up DDRs and initial values on IO ports */
  PORTA = 0x00; // initialize PORTA by clearing data latches
  ANSEL = 0x00; // disable A->D pins
  TRISA = 0b11100100; // only RA4, RA3, RA1, and RA0 are outputs
  WDStatus_out = 0; // deassert status line initially
  DropWeight_out = 0; // deassert relay control line initially
  TRISB = 0b10111111; // only RB6 is output

  /* set up USART */
  BRGH = 1;     /* high speed baud rate */
  SPBRG = 26;   /* 26 = 9481 baud, 12 = 19692 baud, 8 = 28444 baud */
  SYNC = 0;     /* asynchronous */
  SPEN = 1;	/* enable serial port pins */
  TXIE = 0;	/* disable tx interrupts */
  RCIE = 0;	/* disable rx interrupts */
  TX9  = 0;	/* 8-bit transmission */
  RX9  = 0;	/* 8-bit reception */
  CREN = 1;	/* enable reception */
  TXEN = 1;	/* enable the transmitter */

  /* set up on-chip hardware WDT */
  WDTCON = 0b00001001; /* 1:512 pre-scale, WDT on, 2.097 sec time-out with 1:128 post-scale*/
  CLRWDT();

  puts (VERSION_STRING);

  if (!PORb || !BORb || !TOb) { // if real reset, reset timers
    if (!PORb)
      puts ("Power-On Reset Occurred...\xd\xa");
    if (!BORb)
      puts ("Brown-On Reset Occurred...\xd\xa");
    if (!TOb)
      puts ("Hardware watchdog timeout occurred...\xd\xa");
    puts ("Resetting registers\xd\xa");
  } else  {
    puts ("A weird reset occurred... Resetting counting...\xd\xa");
  }
  
  /* set up timers */
  wd_timer = WD_TIMEOUT_DEFAULT;
  bw_timer = 0;

  /* set up interrupts */
  PR2 = 200; /* count up to 200 */
  count = 0; /* reset cascade timer to 0 */
  TMR2 = 0; /* reset timer register */
  T2CON = 0b01111111; /* 1:16 PostScale, Timer2 On, 1:16 PreScale */
  TMR2IE = 1; /* enable Timer2 int */
  PEIE = 1; /* enable peripheral ints */
  GIE = 1; /* enable global ints */

  while (1) { // main loop; do forever

    CLRWDT();

    if (time != time_old) { //print status every second
      time_old = time;
      puts ("wdt = ");
      putintdec(wd_timer, 0);
      puts (",  bwt = ");
      putintdec(bw_timer, 0);
      puts (",  WDC = ");
      putch (WDC_in ? '1' : '0');
      puts (",  ACommsDN = ");
      putch (ACommsDropNow_in ? '1' : '0');
      puts (",  WDSt_out = ");
      putch (WDStatus_out ? '1' : '0');
      puts (",  DW_out = ");
      putch (DropWeight_out ? '1' : '0');
      puts ("\xd\xa");
    }

    if (wd_timer && wd_timer <= 1) { //uh, oh, drop the weight!!!
      puts ("Woof! Turning on the relay to drop the weight\xd\xa");
      DropWeight_out = 1; // turn on relay
      WDStatus_out = 1; // assert status line since relay is on
      wd_timer = 0; // stop counter
      bw_timer = BW_TIMEOUT_DEFAULT;
    }

    if (bw_timer && bw_timer <= 1) { //stop dropping weight
      puts ("Turning off relay now\xd\xa");
      DropWeight_out = 0; // turn off relay
      WDStatus_out = 0; // deassert status line since relay is now off
      wd_timer = WD_TIMEOUT_DEFAULT; // start wd timer again
      bw_timer = 0; // clear bw counter
    }

#if 0
    if (ACommsDropNow_in && !WDStatus_out) { //AComms wants to drop now
      puts ("AComms is dropping the weight now\xd\xa");
      WDStatus_out = 1; // assert status line since relay is on
      wd_timer = 0; // disable WD timer
      bw_timer = BW_TIMEOUT_DEFAULT; // start BW timer
    }

    if (!ACommsDropNow_in && WDStatus_out && !DropWeight_out) { //AComms does not want to drop anymore
      puts ("AComms is clear now from wanting to drop the weight\xd\xa");
      WDStatus_out = 0; // deassert status line since relay is off
      wd_timer = WD_TIMEOUT_DEFAULT; // start wd timer again
      bw_timer = 0; // clear bw counter
    }
#endif

    /* do serial input at this point only */
    if (!(num_chars_in = getCmd (cmd, CMD_MAX_CHARS)))
      continue; // got nothing, so loop around to the beginning
    // got a new message at this point, so deal with it

    /* any serial input that starts with a '!' and ends with '\r' clears WDT */
    if (wd_timer > 1) {
      wd_timer = WD_TIMEOUT_DEFAULT; // start wd timer over
    }

    switch (*cmd) {
    case 'D' : /* drop now */ 
    case 'd' :
      puts ("Dropping weight now from serial command\xd\xa");
      DropWeight_out = 1; //turn on relay
      WDStatus_out = 1; // assert status line since relay is on
      wd_timer = 0;
      bw_timer = BW_TIMEOUT_DEFAULT; // start bw timer
      break;

    case 'R' : /* reset counters */ 
    case 'r' :
      puts ("Resetting counters to default\xd\xa");
      wd_timer = WD_TIMEOUT_DEFAULT; // start wd timer over
      bw_timer = 0; // turn off burnwire timer
      WDStatus_out = 0; // deassert status line since relay is off
      DropWeight_out = 0; // turn off relay
      break;

    case '@' : /* force a hardware watchdog time-out to occur */
      puts ("Woof!!! I will reset in 2 seconds.\xd\xa");
      while (1); // spin forever until watchdog times out
      break; // just for the fun of it

    case 0 : // got a blank message, i.e. WDT Clear "!\r"
      break;

    default : // unknown command
      puts ("Unknown command: "); puts (cmd); puts ("\xd\xa");
      break;
    } // switch (*cmd)

    /* note: do not place any code here, unless you want it to run only 
             when processing serial commands */

  } // while (1)
}
 
