Sunday, May 13, 2012

BevSense v3

Thought I'd give it a more succinct and at the same time livelier name than the absolutely eggheady and dull "liquid level indicator."

Tweaked the firmware and made a number of major changes to reduce current in all modes as well as number of instructions executed when not in standby mode--i.e., when liquid has been detected or if a probe error has occurred. Among the changes are:
  • Only the 31kHz LFINTOSC is used for all modes. With the PWM module taking care of energizing the piezo transducer it's no longer necessary to use the 500kHz clock and toggle it on and off in the ISR. The 500kHz clock is the default frequency upon reset and is used only during initialization of various SFRs.
  • WDT timeout is permanently set to 512 ms.
  • 8 probe readings instead of 5 are stored in PXXval. Because number of instructions has been reduced, even with just a 31kHz clock, time interval between probe reads is usually less than 10ms. So debouncing the probes takes less than 100ms.
  • States _begin, _insta_plo, and _insta_phi, and functions InstaPLO(), InstaPHI(), and DisableAudible have been discarded.
  • Audible indicator is now only disabled when entering standby mode. It isn't necessary to disable it in any other mode since only standby mode needs it shut down.
  • Variables PLOlevel, PHIlevel, plo, phi, buzzing have been discarded. These have been found to be redundant and to increase execution time.
  • Timer1 interrupt is always enabled; only global interrupt is enabled/disabled when audible indicator is enabled/disabled 
  • Timer0 is no longer used. There is now no fixed time between reads when not in standby mode, although the time between probe reads has been determined to be 9.5 to 9.8ms depending on the mode, excluding interrupt service. Measured ISR execution time when low probe is immersed or when both low and high probes are immersed is ~3.1ms or ~3.2ms depending on whether the buzzer is being turned on or off.
  • Probe reading and determination of status/mode have been highly optimized. And for standby mode no function calls outside main() are made, in order to reduce execution time.
  • Although the use of switch / case is more elegant, given the way mikroC implements it, execution time takes a tad longer than using a series of if statements. So in the ISR the probe error routine now uses if / else statements. ISR times using switch / case are ~4.8ms, ~5.3ms, ~5.8ms, ~6.4ms for the first beep, first pause, second beep, second pause, respectively. With if / else times are ~4.3ms, ~4.9ms, ~5.6ms and ~ 5.4ms, respectively.
  • Because of the 31kHz clock, the buzzer frequency of 2.15kHz can no longer be attained with the PWM module. The best that can obtained is 1.94kHz.
  • Left shifting of bits in PXXval is now done after determination of status/mode
With the various improvements average standby mode current is now down to around 0.63 to 0.64µA. When liquid is detected maximum current draw is ~2.0mA and average is ~1.0mA.


/*

Liquid Level Indicator version 3
May 2012

processor = PIC12LF1840
compiler = mikroC v5.6.0

configuration word:
  INTOSC with I/O on clk pin
  enabled: power up timer, WDT via SWDTEN, MCLR, stack over/underflow
  all else disabled

CONFIG1   :$8007 : 0x09CC
CONFIG2   :$8008 : 0x1613

To minimize power consumption BOR should be disabled and MCLR enabled (so that RA3 is not left floating)
Internal oscillator starts at 500kHz after power up then switched to 31kHz LFINTOSC after initialization of SFRs and remains at that setting throughout

*/


#define  int8                unsigned char

#define  on                  1
#define  off                 0

#define  input               1         // for TRISx
#define  output              0         // for TRISx

#define  analog              1         // for ANSELx
#define  digital             0         // for ANSELx

#define  buzz                LATA.f2

#define  an_plo              ANSELA.f1
#define  an_phi              ANSELA.f4
#define  tris_plo            TRISA.f1
#define  tris_phi            TRISA.f4
#define  wpu_plo             WPUA.f1
#define  wpu_phi             WPUA.f4

#define  ch_plo              0         // comparator input channel for low probe
#define  ch_phi              1         // comparator input channel for high probe

#define  t1h_fill            256-15    // TMR1H initial value for audible indicator when low probe immersed or low and high probe immersed
#define  t1h_beep1           256-30    // TMR1H initial value for first beep during probe error
#define  t1h_pause1          256-19    // TMR1H initial value for first pause during probe error
#define  t1h_beep2           256-30    // TMR1H initial value for second beep during probe error
#define  t1h_pause2          256-122   // TMR1H initial value for second pause during probe error

#define  osc31khz            0b0       // 31kHz LFINTOSC, for use with OSCCON
#define  wdt512ms            0b10011   // WDT time out = 512ms, for use with WDTCON

int8 PLOval;                           // stores the last eight low probe readings
int8 PHIval;                           // stores the last eight high probe readings

enum {_bouncing, _standby, _plo_immersed, _plophi_immersed, _phi_immersed}
     STATE = _bouncing,                // current state
     PREVSTATE = _bouncing;            // previous state

enum {_beep1, _pause1, _beep2, _pause2} STATEPERROR;    // for use with buzzer sound pattern when probe error detected


// ===========================================================================================
//       Functions
// ===========================================================================================

void IniReg()
{
  ANSELA = digital;
  TRISA = output;
  PORTA = 0;

  an_plo = analog;
  an_phi = analog;
  tris_plo = input;
  tris_phi = input;

  WPUA = 0;                  // disable individual pull ups
  OPTION_REG = 0;            // global pull ups enabled
  
  DACCON0 = 0;               // DAC off, DAC is not output on DACOUT pin, Vdd as positive source
  //DACCON1 = 0b10000;         // 0x10, Vref = 16/32 = 50% of Vdd
  DACCON1 = 0b11000;         // 0x18, Vref = 24/32 = 75% of Vdd

  CM1CON0 = 0b10;            // comparator off, comp output polarity not inverted, comp output internal only,
                             // comparator in low power low speed mode, hysteresis enabled
  CM1CON1 = 0b10000;         // comparator interrupts disabled, C1VP connected to DAC, C1VN connected to C1N0-

  // piezoelectric transducer empirically determined to be loudest at 2.15kHz
  // Current draw of the transducer has been determined to be directly proportional to duty cycle.
  // Audio volume of the transducer is proportional to duty cycle, but whether it is linear, logarithmic, or otherwise, is unknown
  // PWM Period = (PR2 + 1) x 4 x Tosc x (TMR2 Prescale Value), where Tosc = 1/Fosc
  // Duty Cycle = (CCPR1L:CCP1CON<5:4>) / [4 x (PR2 + 1)]
  // Given PR2 = 3, timer2 prescale = 1, CCPR1L:CCP1CON<5:4> = 4, Fosc = 31kHz,
  // PWM period = 516us (freq = 1.937kHz) and duty cycle = 25%
  T2CON = 0;                 // prescale = 1:1, postscale = 1:1, timer2 off
  PR2 = 3;
  CCPR1L = 0b1;              // with CCPR1L = 0b1 and CCP1CON<5:4> = 0b00, CCPR1L:CCP1CON<5:4> = 0b100 = 4 decimal
  CCP1CON = 0b1100;          // PWM mode with P1A active high, P1B disabled, CCP1CON<5:4> = 0b00

  // initialize probe readings to zero and levels to zero
  PLOval = 0;
  PHIval = 0;

  PIE1.TMR1IE = 1;           // timer1 interrupt enabled
  INTCON.PEIE = 1;           // peripheral interrupt enabled. Global interrupt is by default disabled upon any reset. GIE is enabled only when timer1 is enabled (i.e., when buzzer needs to be sounded)
  OSCCON = osc31khz;         // default INTOSC frequency = 500khz. Change to 31kHz LFINTOSC after initialization.
  WDTCON = wdt512ms;         // WDT time out = 512ms. This is the sleep time between probe reads when in Standby mode
                             // longer sleep times would be better from a power consumption perspective
                             // but would be too long when the user starts using the unit for its intended purpose -- to detect liquid and as soon as possible

} // void IniReg()


// turn on buzzer
void BuzzOn()
{
  TMR2 = 0;
  PIR1.TMR2IF = 0;
  T2CON.TMR2ON = 1;
}

// turn off buzzer
void BuzzOff()
{
  buzz = off;
  T2CON.TMR2ON = 0;
}

// audible indicator is armed, with the particular sequence of sounds emitted depending on
// whether liquid has reached low probe, low and high probe, or just the high probe
void EnableAudible()
{
  TMR1L = 0;              
  PIR1.TMR1IF = 0;
  INTCON.GIE = 1;
  BuzzOn();
}

// both low and high probes not immersed
// PLOval = PHIval = 0
void Standby()
{
  STATE = _standby;
  BuzzOff();                // following three statements disable audible indicator
  T1CON.TMR1ON = 0;         //  
  INTCON.GIE = 0;           // 
  wpu_plo = 1;              // enable weak pullup for low probe
  wpu_phi = 1;              // enable weak pullup for high probe
}

// low probe immersed -- cup nearly full
void PLOimmersed()
{
  if (PREVSTATE != _plo_immersed)
  {
    STATE = _plo_immersed;
    TMR1H = t1h_fill;
    T1CON = 0b1100001;       // Timer1 clock source is system clock (FOSC), prescale = 1:4, timer1 oscillator off, timer1 on
                             // given 31kHz clock and TMR1H ini value = 15, timer1 tick is ~500ms
    EnableAudible();
  }
}

// low and high probes both immersed -- cup is full
void PLOPHIimmersed()
{
  if (PREVSTATE != _plophi_immersed)
  {
    STATE = _plophi_immersed;
    TMR1H = t1h_fill;
    T1CON = 0b1000001;       // Timer1 clock source is system clock (FOSC), prescale = 1:1, timer1 oscillator off, timer1 on
                             // given 31kHz clock and TMR1H ini value = 15, timer1 tick is ~125ms
    EnableAudible();
  }
}

// probe error -- only high probe is immersed
void PHIimmersed()
{
  if (PREVSTATE != _phi_immersed)
  {
    STATE = _phi_immersed;
    TMR1H = t1h_beep1;
    T1CON = 0b1000001;       // Timer1 clock source is system clock (FOSC), prescale = 1:1, timer1 oscillator off, timer1 on
    EnableAudible();
    STATEPERROR = _beep1;
  }
}


void DetermineState()
{
  asm clrwdt

  // ===============================
  // Read Probes -- for modes other than standby
  // Individual weak pull ups are enabled prior to reading and disabled after reading to minimize current draw since the probes are immersed and weak pull ups will draw current
  // DAC, weak pull ups, and comparator are turned on before reading and then turned off afterwards to minimize power consumption
  // current reading stored in PXXval.f0
  // ===============================
  CM1CON0.C1ON = 1;          // turn on comparator (current draw ~4.5uA)
  DACCON0.DACEN = 1;         // turn on DAC (current draw ~19uA)

  wpu_plo = 1;               // enable low probe weak pull up
  CM1CON1.C1NCH = ch_plo;
  if (CMOUT)
    PLOval.f0 = 1;
  wpu_plo = 0;               // disable low probe weak pull up

  wpu_phi = 1;               // enable high probe weak pull up
  CM1CON1.C1NCH = ch_phi;
  if (CMOUT)
    PHIval.f0 = 1;
  wpu_phi = 0;               // disable high probe weak pull up

  DACCON0.DACEN = 0;         // turn off DAC
  CM1CON0.C1ON = 0;          // turn off comparator

  // ===============================
  // Determine state/mode
  // ===============================
  if (!PHIval)
  {
    if (!PLOval)
      Standby();
    else if (PLOval == 0xFF)
      PLOimmersed();
  }
  else if (PHIval == 0xFF)
  {
    if (PLOval == 0xFF)
      PLOPHIimmersed();
    else if (!PLOval)
      PHIimmersed();
  }

  // all bits (readings) of PXXval can now be shifted left
  // discard PXXval.f7 and make PXXval.f0 ready for the new reading
  PLOval <<= 1;
  PHIval <<= 1;

  PREVSTATE = STATE;
} // void DetermineState()


void interrupt()
{
  if (PIR1.TMR1IF)
  {
    if (STATE == _phi_immersed)
    {
      if (++STATEPERROR > _pause2)
        STATEPERROR = _beep1;

      if (!STATEPERROR)
      {
        BuzzOn();
        TMR1H = t1h_beep1;           // given 31kHz, system clock as clock source, prescale = 1:1, TMR1H ini value = 30, timer1 tick is ~250ms
      }
      else if (STATEPERROR == _pause1)
      {
        BuzzOff();
        TMR1H = t1h_pause1;          // given 31kHz, system clock as clock source, prescale = 1:1, TMR1H ini value = 19, timer1 tick is ~160ms
      }
      else if (STATEPERROR == _beep2)
      {
        BuzzOn();
        TMR1H = t1h_beep2;           // given 31kHz, system clock as clock source, prescale = 1:1, TMR1H ini value = 30, timer1 tick is ~250ms
      }
      else
      {
        BuzzOff();
        TMR1H = t1h_pause2;          // given 31kHz, system clock as clock source, prescale = 1:1, TMR1H ini value = 122, timer1 tick is ~1000ms
      }
    } // if (STATE == _phi_immersed)
    else // if (STATE == _plo_immersed || STATE == _plophi_immersed)
    {
      TMR1H = t1h_fill;
      if (T2CON.TMR2ON)
        BuzzOff();
      else
        BuzzOn();
    }
    PIR1.TMR1IF = 0;
  } // if (PIR1.TMR1IF)
} // void interrupt()


void main()
{
  IniReg();

  while(1)
  {
    if (STATE != _standby)
      DetermineState();      // just one function call to reduce execution time
    else
    {
      asm sleep              // WDT is automatically cleared right before sleep and after waking up
                             // MCU wakes up upon WDT timeout

      // Reduction of MCU awake time during Standby mode reduces current draw
      // Ways by which instructions executed when MCU is awake are reduced:
      //     1. Probe reading is stored in PXXval only if reading is high
      //     2. Weak pull ups are kept on since they don't consume current unless probes are immersed.
      //     3. Standby mode is when PLOval = PHIval = PLOlevel = PHIlevel = 0 therefore we need only monitor for instantaneous readings that are high
      //     4. Reduction/elimination of function calls and using inline code instead
      // DAC and voltage comparator are turned on before reading and then turned off afterwards to minimize power consumption
      DACCON0.DACEN = 1;     // turn on DAC
      CM1CON0.C1ON = 1;      // turn on comparator

      CM1CON1.C1NCH = ch_phi;
      if (CMOUT)
      {
        STATE = _bouncing;
        PLOval.f0 = 1;
      }

      CM1CON1.C1NCH = ch_plo;
      if (CMOUT)
      {
        STATE = _bouncing;
        PHIval.f0 = 1;
      }

      CM1CON0.C1ON = 0;      // turn off comparator
      DACCON0.DACEN = 0;     // turn off DAC
    }
  } // while(1)
} // void main()

No comments:

Post a Comment