/*********************************  sleepy.c  *******************************
 * $Source: /home/cvs/ESP/gen2/software/msp430/sleepy/sleepy.c,v $
 *  Copyright (C) 2003 MBARI
 *
 *  MBARI Proprietary Information. All rights reserved.
 * $Id: sleepy.c,v 1.83 2005/10/23 03:29:07 brent Exp $
 *
 * Mainline initialization and background loop for the "sleepy"
 * dwarf microcontroller of the gen2 ESP.
 *
 *****************************************************************************/

//#define I2CtraceDepth 16

static char const description[] = "ESP2 v2.00";

// Clock rates
#define CPURATE   6000000 //target CPU clock in hz (may be adjusted)
#define BAUDRATE  115200  //desired USART baud rate
#define I2CRATE   100000  //standard I2C bit rate
#define BLINKRATE 1*TIMERHZ  //adjust the DCO every second & blink LED

#include "sleepydwarf.h"  //hardware specific to sleepy
#include "dwarftop.h"     //headers required for later inclusion of dwarfmain.h
#include "dwarf.h"        //register and I/O definitions common to all dwarves
#include "I2Cgate.h"      //parse messages from host

// FIFO sizes and Handshaking thresholds
static FIFO (toHost, 300);      //bytes bound for host machine
static FIFO (fromHost, 900);    //bytes received from host machine
#define CTSstop     150    //cancel CTS if fifo has < this many more bytes free
#define CTSresume   200    //reassert CTS when input fifo has this much room

//these just save typing the instance parameter over and over
#define hold()          I2CgateHold(&gateway)
#define release()       I2CgateRelease(&gateway)
#define putByte(byte)   parserPutByte(&gateway.parser,(byte))
#define putWord(word)   parserPutWord(&gateway.parser,(word))
#define putTime(word24) parserPut24(&gateway.parser,(word24))
#define getByte()       parserGetByte(&gateway.parser)
#define getWord()       parserGetWord(&gateway.parser)
#define kick()          parserKickstart(&gateway.parser)
#define putString(s)    parserPutString(&gateway.parser,(s))

#define put8(byte)      FIFOwriteByteW(&toHost,(byte))
#define put16(word)     FIFOwrite16(&toHost, (word))
#define put24(time)     FIFOwrite24(&toHost, (time))
#define kick()          parserKickstart(&gateway.parser)

// Software timers (each decriments TIMERHZ counts/second)
enum timerIndex {   //allocate and name interval timers
  dcoTimer,         //adjust DCO frequency to compensate for temperature change
  
  NTIMERS   //number of interval timers
};

#define WATER_DEBOUNCE 5      //five consequtive water alarms cause shut down
#define WATER_SHUTTIME 4      //allow host four seconds to shutdown
#define WATER_FILTERMAX 30    //saturation value for waterAlarm

#define SOS_RADIO_ON   15     //radio on for 15 seconds to broadcast water alarm
#define SOS_RADIO_OFF  5      //then, radio off for 5 seconds

static uint16 envRate = 0;   //seconds per env sensor reports
static uint16 envCount = 0;  //seconds til next env sensor report

static byte waterAlarm = 0;  //seconds until water alarm
  //incremented every time water alarm is indicated by ADC input
  //shutdown when > WATER_DEBOUNCE+WATER_SHUTDOWN
#define WaterInCan()  (waterAlarm > WATER_DEBOUNCE+WATER_SHUTTIME)

static uint16 powerImage = SLPYSYSPWR;  //current power control mask

static byte SOScountdown;  //countdown in secs to next phases of SOS signal

static enum { //sundry internal state flags
  envNoticePending      = 0x1,  //host is due for an environment sensor update
  shutdownNoticePending = 0x2   //host is due a shutdown warning
} flags = 0;

#define noticesPending (envNoticePending|shutdownNoticePending)


static I2Cvectors I2Cstate;  //current state of I2C msg processing

static void sendNotices(I2CgateInstance *instance);

static 
 boolean sleepyCmd (I2CgateInstance *instance, unsigned code, unsigned length);
 
//a complete description of the I2C gateway message parser
static I2CgateInstance gateway = {
  {&fromHost,&toHost,pollIn,kickstartXmit,startResync},  //base parser "object"
  &I2Cstate,            //I2C bus interrupt function table
  sendNotices,          //send host any pending asynchronous notices
  sleepyCmd,            //process any command the gateway does not understand
  description,          //short description of application
  I2Clow, I2Chigh       //I2C clock waveform in SMCLKS
};


#define ADCIE (ADC12IE)

static inline void enableADC(void)
{
     ADCIE = 0x80;  //enable adcisr only on last conversion 
}      

static inline void disableADC(void)
{
     ADCIE = 0;    //disable ADC isrs
}  

static void updatePower(void)
{
  writeDWRFfirst (SLPYPWRCTL, powerImage);
  writeDWRFlast (powerImage >> 8);
}

static void powerOff (uint16 mask2clear)
{
  _DINT(); _NOP();
  powerImage &= ~mask2clear;
  updatePower();
  _EINT();
}

static void powerOn (uint16 mask2set)
{
  _DINT(); _NOP();
  powerImage |= mask2set;
  updatePower();
  _EINT();
}

static void powerQuery (uint16 mask2query)
// output '0' if power is off,'1' if it is on.
{
  unsigned pwrAck = '0';
  if (powerImage & mask2query) pwrAck='1';
  hold();
  putByte (pwrAck);
  release();
}


//attempt to send async notices if it is known that one is pending
//call with interrupts disabled -- GIE unknown on return
static inline void trySendingQuick (void)
{
  if (!I2CgateBusy(I2Cstate)) {
    I2CgateHoldWhenNotBusy(&gateway);
    _EINT();
    release();
  }
}


//attempt to send async notices from within an ISR
//call with interrupts disabled -- may enable interrupts briefly
static inline void trySendingNotices (void)
{
  if ((flags & noticesPending) && !I2CgateBusy(I2Cstate)) {
    I2CgateHoldWhenNotBusy(&gateway);
    _EINT();
    release();
    _DINT();
  }
}



//hook into dwarfmain.h to process CPLD interrupts
static inline void serviceDwarfIRQ (byte pending)
/*
  Service Pending Dwarf Interrupts
  Global Interrupts are enabled on entry.
*/
{
  if (pending & DWRFI2CHELD) I2CgateCkHeld (&gateway);

//add more CPLD interrupt handler calls here...
//each MUST clear the source of the interrupt or mask it before exit
}


// specify framework's hooks for this application
#define dwarfTic()       I2CgateTic(&gateway)
#define dwarfInResume()  I2CgateInResume(&gateway)
#define dwarfOutResume() I2CgateOutResume(&gateway)
#define dwarfInErr(mask) I2CgateInErr (&gateway, (mask))
#define DWARFI2CISRargs  &gateway
#define DWARFdebugPort   &gateway.parser  //shared with host
#define DWARFI2Cstate    I2Cstate

//hook into dwarfmain.h to process rising I2C clock edge interrupts
#define DWRFEDGEIRQ(edges) \
  if (edges & DWRFI2CSCL) I2CgateCkEdge (&gateway)

//hook into dwarfmain.h at the end of each I2C ISR
//this sends any pending asynchronous notices
#define DWARFI2CendISR(ignoredInstance) trySendingNotices()
    

//// BEGIN Framework
#include "dwarfmain.h"   //generic dwarf application "framework"
//// END Framework


static inline void adcStart (void)
{
  //start ADC conversion
  ADC12CTL0|=ADC12SC;
}

static void normalOperation (
    struct timer *array, unsigned index, struct timer *timer)
{
  adcStart();             //start ADC for environmental sensors
  dwarfTrimDCO();         //this should happen at least once a second
  SLPYLEDOUT ^= SLPYYEL;  //toggle the yellow LED on every DCO adjustment
  if (envRate && !--envCount) {
    envCount = envRate;
    _DINT();
    flags |= envNoticePending;
    trySendingQuick();
    _EINT();
  }
}


static void sendSOSoff (  //forward declaration
    struct timer *array, unsigned index, struct timer *timer);
    
static void sendSOSon (
    struct timer *array, unsigned index, struct timer *timer)
{
  normalOperation (array, index, timer);
#if SOS_RADIO_ON
  if (!--SOScountdown) {
    powerOff (-1);
    SOScountdown = SOS_RADIO_OFF;
    itimer[dcoTimer].action = sendSOSoff;  //back to radio off phase    
  }
#endif
}


static void sendSOSoff (
    struct timer *array, unsigned index, struct timer *timer)
{
  normalOperation (array, index, timer);
#if SOS_RADIO_OFF
  if (!--SOScountdown) {
    powerOn (SLPYMODEMPWR);
    SOScountdown = SOS_RADIO_ON;
    itimer[dcoTimer].action = sendSOSon;  //radio on phase    
  }
#endif
}

static void dwarvesReady (
    struct timer *array, unsigned index, struct timer *timer)
{
  itimer[dcoTimer].action = normalOperation; 
  normalOperation (array, index, timer);
}


static void unlockDwarves (
    struct timer *array, unsigned index, struct timer *timer)
{
  normalOperation (array, index, timer);
  _DINT();
  I2CgateUnlock(&gateway);  //unlock the bus
  _EINT();
  itimer[dcoTimer].countdown=TIMERHZ/2; //allow .5 sec for dwarf address assign
  itimer[dcoTimer].action = dwarvesReady; 
  SLPYLEDOUT |= SLPYGRN;  //i2c bus is ready
}


        
static void sendEnvReport (unsigned reportCmdCode)
// send an environmental sensor report with the specified reportCmdCode
//must be called with interrupts disabled and output "held"     
{  //writes 16 bytes to host
  byte adcInts = ADCIE;
  put8 (0x8F); put8(reportCmdCode); //ouput length and command code
  _EINT(); _DINT();  //don't miss input while writing output
  disableADC();
  put16 (SLPYWATER);
  put16 (SLPYHUMID);
  put16 (SLPYTEMPER);
  _EINT(); _DINT(); _NOP(); 
  put16 (SLPYABSPRESS);
  put16 (SLPYBATVOLT);
  _EINT(); _DINT(); _NOP();
  put16 (SLPYSYSCURRFST);
  put16 (SLPYSYSCURRSLW);
  ADCIE = adcInts;
}


static 
 boolean sleepyCmd (I2CgateInstance *instance, unsigned code, unsigned length)
/*
  the next length bytes of data in the input fifo contain the body
  of a sleep command
*/
{
  unsigned cmd = -(code|~0x7f); //remap command codes such that 0x7f ==> 1, etc.
  switch (cmd) {
    case 6:  //"start" powered peripherals on the I2C bus
      hold();
      MASK_TIMER_INTERRUPTS();
      disableADC();
      if (WaterInCan())
        putByte('!');  //we won't start with water in the can
      else {
        timerAction *act = itimer[dcoTimer].action;
        if (act != unlockDwarves && act != dwarvesReady)
          if (powerImage & SLPYDWARFPWR)
            putByte('1');  //bus is powered and ready
          else {
            _DINT();
            I2CgateLock(instance);  //lock the bus
            _EINT();
            SLPYLEDOUT &= ~SLPYGRN;  //indicate bus is locked
            powerOn(SLPYDWARFPWR);
            itimer[dcoTimer].countdown=TIMERHZ/2; //allow .5 sec for dwarf reset
            itimer[dcoTimer].action = unlockDwarves;
            putByte('0');  //powering up bus
          }
        else
          putByte('.');  //bus is already starting     
      }
      enableADC();
      UNMASK_TIMER_INTERRUPTS();
      release();
      break;
                
     
    case 10:  //power camera down
      powerOff(SLPYCAMERAPWR);
      break;
      
    case 11:  //power camera up
      powerOn(SLPYCAMERAPWR);
      break;
      
    case 12:  //camera power query
      powerQuery(SLPYCAMERAPWR);
      break;
      
    case 13:  //power servo motors down
      powerOff(SLPYRAWPWR);
      break;
      
    case 14:  //power servo motors up
      powerOn(SLPYRAWPWR);
      break;
      
    case 15:  //servo motor power query
      powerQuery(SLPYRAWPWR);
      break;
      
    case 16:  //power dwarves down
      powerOff(SLPYDWARFPWR);
      break;
      
    case 17:  //power dwarves up
      powerOn(SLPYDWARFPWR);
      break;
      
    case 18:  //dwarves power query
      powerQuery(SLPYDWARFPWR);
      break;
    
    case 22:  //Environmental sensor query
      if (length) {
        unsigned newRate = length > 1 ? getWord() : getByte();
        MASK_TIMER_INTERRUPTS();
        envRate = envCount = newRate;
        UNMASK_TIMER_INTERRUPTS();
      }else{
        hold();
        _DINT();
        sendEnvReport(-22);
        kick();
        _EINT();
        release();
      }
      break;
      
    default:
      return false;  //force a resync on an illegal command
  }
  return true;
}


//send any pending asynchronous notices back to host
//call with I2CgateHeld and interrupts ENABLED
//may be called from background or interrupt contexts
static void sendNotices(I2CgateInstance *instance)
{
  if (flags & noticesPending) {
    _DINT();
    if (FIFOfree(&toHost) > 21) {  //only if room for all possible notices...
      if (flags & shutdownNoticePending) {  //writes 5 bytes
        flags &= ~shutdownNoticePending;
        put8(0x84); put8(-3);   //ouput length and command code
        put24(WATER_SHUTTIME);
        _EINT(); _DINT(); _NOP();
      }
      if (flags & envNoticePending) {
        flags &= ~envNoticePending;
        sendEnvReport (-23);
      }
      kick();
    }
    _EINT();
  }
}


//// ADC interrupt handler

static inline void initAdc(void)
{

     P6SEL=0xff;//disable I/O on all port 6 pins (see note on ADC port pg17-5 in users manual)
     P6DIR=0;	//Set all port 6 pins to be inputs

     //set ADC control register 0
     ADC12CTL1=SHP+ADC12SSEL_3+CONSEQ_1; //use SMCLK as source, pulse mode sampling, sequence of conversions mode
     ADC12MCTL0=SREF_1+INCH_0;
     ADC12MCTL1=SREF_1+INCH_1;
     ADC12MCTL2=SREF_1+INCH_2;
     ADC12MCTL3=SREF_1+INCH_3;
     ADC12MCTL4=SREF_1+INCH_4;
     ADC12MCTL5=SREF_1+INCH_5; 
     ADC12MCTL6=SREF_1+INCH_6;
     ADC12MCTL7=SREF_1+INCH_7+EOS;
     ADC12CTL0=SHT0_15+SHT1_15+MSC+REF2_5V+REFON+ADC12ON+ENC; //ADC config 
     enableADC();
}

interrupt[ADC_VECTOR] void adcIsr(void)
//called (like all ISRs!) with interrupts DISABLED
//so, we must enable them for cases where we don't return in < 40 microseconds
{
  if (ADC12IV == 20)  { //this IV for memory register 7 prog for ADC ch 7
      //sequence completed - process data
 
    if (SLPYWATER > SLPYWET) {  //looks like there may be water in the can!
      if (++waterAlarm > WATER_DEBOUNCE) {
        if (powerImage & ~SLPYMODEMPWR) {  //something more than modem powered
          if (waterAlarm == WATER_DEBOUNCE+1) { //tell host to clean up
            flags |= shutdownNoticePending;
            allowINTinISR();   //allow interrupt to be enabled     
            disableADC();
            trySendingQuick();
            _DINT();
            enableADC();  
            propagateEV(ADC);  
          }else if (WaterInCan()) { //shut down NOW!
            MASK_TIMER_INTERRUPTS();
            SLPYLEDOUT |= SLPYRED; //red LED to indicate host is being shut down
  #if SOS_RADIO_OFF || SOS_RADIO_ON
  #if SOS_RADIO_OFF
            powerImage=0;
            updatePower();
            SOScountdown = SOS_RADIO_OFF;
            itimer[dcoTimer].action = sendSOSoff;  //radio off phase
  #elif SOS_RADIO_ON
            powerImage = SLPYMODEMPWR;
            updatePower();
            SOScountdown = SOS_RADIO_ON;
            itimer[dcoTimer].action = sendSOSon;  //radio on phase
  #endif
  #else
            powerImage=0;
            updatePower ();
            itimer[dcoTimer].action = sendSOSoff;  //radio off phase
  #endif
            UNMASK_TIMER_INTERRUPTS();
          }
        }
        if (waterAlarm > WATER_FILTERMAX) waterAlarm = WATER_FILTERMAX;
      }
    }else
      if (waterAlarm) --waterAlarm;  //drive debounce filter back to zero
      
    {
      volatile unsigned int ADC7 = SLPYUNUSED; //read the last to reset IFG
    }
  }	 
}
//// END ADC interrupt



//  Application's initialization and background loop

void main(void)
{
  SLPYLEDOUT = -1;                //turn on all LEDS
  SLPYLEDDIR = ~(SLPYLEDSEL = 0); //turn off green and yellow leds
  initAdc();

  dwarfMainInit();             // initialize framework
  
  itimer[dcoTimer].action = normalOperation;  //start blinking yellow LED
  itimer[dcoTimer].countdown = itimer[dcoTimer].period = BLINKRATE; 
    
  SLPYCTLOUT &= ~SLPYCMDRXDI;   // enable cmd port receiver
  SLPYCTLOUT |= SLPYCMDTXEN|SLPYENVON|SLPYMUX104;  //cmd port tx, env sensor
                               // & let PC/104 control the octal usart

  writeDWRF (SLPY104ADR, 0x4); // set octal usart base address to 0x400
  
  // make power ctrl reg match initial default powerImage
  updatePower();
  
  // connect and enable all the serial ports
  writeDWRF (SLPYLOOP, 0);
  writeDWRF (SLPYCONNECT, -1);  //start pulse to connect all ports
  writeDWRF (SLPYRXDI, 0);
  writeDWRF (SLPYTXEN, -1);
  
  I2CgateInit (&gateway);      //one-time init of I2C processor

  MCLKsleep (MCLKRATE/300);    //end 3ms+ initial port connection pulse
  writeDWRF (SLPYCONNECT, 0);
  
  if (setjmp (restartEnv))
    dwarfMainRestart();
  else {
    SLPYLEDOUT |= SLPYRED;   //red LED indicates we're waiting for host to sync
    resynchronize();         //wait for initial BREAK after hard reset
  }

  dwarfEnableRTS();          //always require RTS from Host
  SLPYLEDOUT &= ~SLPYRED;    //turn off red LED
  I2CgateStart (&gateway);   //(re-)start the message parser
  SLPYLEDOUT |= SLPYRED;     //red LED indicates we're waiting for host to sync
  resynchronize();
  RESET();  //just in case we somehow get here!
}
