To address these issues and remain within the limits of the battery, I've decreased the pulse duration to the transducer by reducing the duty cycle from 50% to 24%. As it was, achieving the 50% duty cycle the firmware used timer2 interrupt to toggle pin RA2 output. To implement a DC other than 50% I've opted to enable the PWM module. RA2 is also the CCP1 pin (one of two alternate pins) so this isn't a problem.
I tested various duty cycle values and recorded the associated current. Bear in mind that for this test I modified the firmware slightly so that the buzzer sounds continuously--there are no pauses wherein the buzzer is turned off--when probes are immersed. Here are the values I obtained.
| Duty Cycle (%) | Average Current (mA) |
|---|---|
| 50 | 3.99 |
| 24 | 2.154 |
| 12 | 1.162 |
| 5 | 0.545 |
I then posted the numbers on a spreadsheet and had it perform a simple linear regression analysis. Turns out the correlation is >99%. Coefficient a = 7.60 and constant b = 0.23. So the equation--valid for DC = 5% to 50% and series capacitance = 1uF--is
I = aDC + b = 7.6DC + 0.23
where I = current, DC = duty cycle. That b is nonzero befuddled me for a moment since when DC = 0, current should be zero. Then I realized b is the overheard current--this is current flowing through the various microcontroller modules including the MFINTOSC, comparator, DAC, weak pull-ups, and WDT.
Unfortunately I don't have a sound level meter to measure the change in audio volume. Volume definitely is reduced as DC is lowered but I don't know the exact relationship. Nevertheless, halving DC to 24% does not greatly diminish the sound level and is still very much audible. In the firmware below I use 24% as the duty cycle. I chose this value because the maximum current that the transducer draws at this DC is around 2mA which in the CR2032 datasheet translates to a discharge capacity of around 200mAh. We can reduce DC further of course but transducer volume suffers. So the DC value is a compromise between battery life and buzzer volume.
On a different note (ha! pun unintended or is it?), I've been able to further reduce the current during standby mode by taking down 4 statements which correspond to 4 assembly instructions. According to my tests the internal weak pull-ups don't consume any current unless the input pins have a path to ground--meaning, in the case of our circuit, liquid is detected or the pin is shorted to ground. So what I did was to keep the global and individual weak pull-up register bits enabled during standby mode. But once liquid is detected the individual weak pull-ups are enabled only during probe reads. As you'll see in the firmware, there are now two functions for probe reads:
ReadProbesV1() and ReadProbesV2(). There's loads of unused Flash memory (only around 10% is used) so having two nearly identical functions albeit with a net result of lower power consumption makes a lot of sense. Measurements show that maximum current has dropped from ~2.54 to ~2.05µA (this is the current draw when the MCU is awake) and average current from ~0.75µA to ~0.69µA. The latter is an 8% reduction. That isn't too bad I think.Just to make sure setting unused pins as digital output is really better than configuring them as inputs, I compared the currents for the various configurations.
| Configuration | Current (µA) | ||
|---|---|---|---|
| Max | Min | Ave | |
| All unused pins are digital inputs | 12.57 | 8.81 | 9.33 |
| All unused pins are analog inputs | 8.01 | 4.95 | 5.31 |
| All unused pins are digital outputs (with RAx pulled to ground) | 2.54 | 0.36 | 0.74 |
Without a shadow of a doubt, digital output pins win hands down.
Below is the latest version of the firmware. Besides the changes mentioned above, there are other minor ones which don't significantly affect power consumption.
/*
Liquid Level Indicator version 2
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)
*/
#define int1 bit
#define int8 unsigned char
#define int16 unsigned int
#define int32 unsigned long
#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 hi 1 // switch level high
#define lo 0 // switch level low
#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-30 // TMR1H initial value, for audible indicator when low probe immersed or low and high probe immersed
#define t1h_beep1 256-31 // 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-31 // TMR1H initial value, for second beep during probe error
#define t1h_pause2 256-122 // TMR1H initial value, for second pause during probe error
#define osc500khz 0b111000 // 500kHz MFINTOSC, for use with OSCCON
#define osc31khz 0b0 // 31kHz LFINTOSC, for use with OSCCON
#define wdt8ms 0b111 // value for WDTCON
#define wdt16ms 0b1001 // value for WDTCON
#define wdt32ms 0b1011 // value for WDTCON
#define wdt128ms 0b1111 // value for WDTCON
#define wdt256ms 0b10001 // value for WDTCON
#define wdt512ms 0b10011 // value for WDTCON
int8 PLOval; // last five low probe readings
int8 PHIval; // last five high probe readings
bit PLOlevel; // voltage level of low probe when not bouncing (hi = 1, lo = 0)
bit PHIlevel; // voltage level of high probe when not bouncing (hi = 1, lo = 0)
bit plo; // comparator reading of low probe. 1 = liquid detected, 0 = no detection
bit phi; // comparator reading of high probe. 1 = liquid detected, 0 = no detection
bit buzzing; // buzzer status flag; 1 = buzzer is on, 0 = buzzer off
enum {_begin, _standby, _insta_plo, _immersed_plo, _insta_phi, _immersed_phi, _probe_error}
STATE = _begin, // current state
PREVSTATE = _begin; // 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 = 0b00000010; // global pull ups enabled, timer0 prescale = 1:8
// given clock = 500khz, TMR0 increments from zero to 255, prescale = 1:8, timer0 tick = 256*8 / (500kHz/4) = 16.384ms
wpu_plo = 1;
wpu_phi = 1;
DACCON0 = 0; // DAC off, DAC is not output on DACOUT pin, Vdd as positive source
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
// transducer is energized at a fixed frequency of 2.155kHz and fixed duty cycle of 24%
// 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 = 57, timer2 prescale = 1, CCPR1L:CCP1CON<5:4> = 56, Fosc = 500kHz,
// PWM period = 464us (freq = 2.155kHz) and duty cycle = 24%
PR2 = 57;
T2CON = 0; // prescale = 1:1, postscale = 1:1, timer2 off
CCPR1L = 0b1110; // with CCPR1L = 0b1110 and CCP1CON<5:4> = 0b00, CCPR1L:CCP1CON<5:4> = 0b111000 = 56 decimal
INTCON.GIE = 0;
INTCON.PEIE = 1;
// initialize probe readings to zero and level to zero
PLOval = 0;
PHIval = 0;
PLOlevel = lo;
PHIlevel = lo;
buzzing = 0;
OSCCON = osc31khz; // default INTOSC frequency = 500khz. Change to 31kHz LFINTOSC after initialization.
// in all probability when coin battery is inserted and circuit powered up, the probes are not immersed in liquid and so the MCU will be put to sleep
} // void IniReg()
// turn on buzzer
void BuzzOn()
{
TMR2 = 0;
PIR1.TMR2IF = 0;
CCP1CON = 0b1100; // PWM mode with P1A active high, P1B disabled, CCP1CON<5:4> = 0b00
T2CON.TMR2ON = 1;
buzzing = 1;
}
// turn off buzzer
void BuzzOff()
{
buzz = off;
T2CON.TMR2ON = 0;
CCP1CON = 0; // CCP off
buzzing = 0;
}
// audible indicator is armed and the specific sequence of sounds emitted depends on
// whether liquid has reached low probe or high probe or whether a probe error has occurred.
void EnableAudible()
{
TMR1L = 0;
PIR1.TMR1IF = 0;
PIE1.TMR1IE = 1;
INTCON.GIE = 1;
BuzzOn();
}
void DisableAudible()
{
BuzzOff();
T1CON.TMR1ON = 0;
PIE1.TMR1IE = 0;
INTCON.GIE = 0;
}
// ReadProbes Version 1 -- for Standby mode only -- weak pull ups are always on since weak pull ups don't consume current unless probes are immersed.
// reducing the number of instructions (enabling and disabling weak pull ups) reduces time when MCU is awake and therefore reduces current draw
// voltage comparator reads the probes
// Weak pull ups are assumed to be already enabled. DAC and comparator are turned on before reading and then turned off afterwards to minimize power consumption
void ReadProbesV1()
{
DACCON0.DACEN = 1; // turn on DAC
CM1CON0.C1ON = 1; // turn on comparator
CM1CON1.C1NCH = ch_plo;
if (CMOUT)
plo = 1;
else
plo = 0;
CM1CON1.C1NCH = ch_phi;
if (CMOUT)
phi = 1;
else
phi = 0;
CM1CON0.C1ON = 0; // turn off comparator
DACCON0.DACEN = 0; // turn off DAC
} // void ReadProbesV1()
// ReadProbes Version 2 -- for all other modes except Standby.
// Individual weak pull ups are enabled and disabled to minimize current draw since the probes are immersed and weak pull ups will draw current
// voltage comparator reads the probes
// DAC, weak pull ups, and comparator are turned on before reading and then turned off afterwards to minimize power consumption
void ReadProbesV2()
{
DACCON0.DACEN = 1; // turn on DAC
CM1CON0.C1ON = 1; // turn on comparator
wpu_plo = 1; // enable low probe weak pull up
CM1CON1.C1NCH = ch_plo;
if (CMOUT)
plo = 1;
else
plo = 0;
wpu_plo = 0; // disable low probe weak pull up
wpu_phi = 1; // enable high probe weak pull up
CM1CON1.C1NCH = ch_phi;
if (CMOUT)
phi = 1;
else
phi = 0;
wpu_phi = 0; // disable high probe weak pull up
CM1CON0.C1ON = 0; // turn off comparator
DACCON0.DACEN = 0; // turn off DAC
} // void ReadProbesV2()
void Debounce()
{
// shift all bits to the left
// if probe reading is high then PXXval bit 0 = 1
PLOval <<= 1;
if (plo)
++PLOval;
PLOval.f5 = 0; // only bits 0 to 4 (5 least significant bits) are used so bits 5 to 7 must be cleared
// need only clear bit 5 because this zero will eventually be left shifted into bits 6 and 7
PHIval <<= 1;
if (phi)
++PHIval;
PHIval.f5 = 0; // only bits 0 to 4 (5 least significant bits) are used so bits 5 to 7 must be cleared
// need only clear bit 5 because this zero will eventually be left shifted into bits 6 and 7
if (!PLOlevel)
{
if (PLOval == 0b11111)
PLOlevel = hi;
}
else if (!PLOval)
PLOlevel = lo;
if (!PHIlevel)
{
if (PHIval == 0b11111)
PHIlevel = hi;
}
else if (!PHIval)
PHIlevel = lo;
} // void DebounceSwitch()
void Standby()
{
if (PREVSTATE != _standby)
{
STATE = _standby;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
WDTCON = wdt512ms;
wpu_plo = 1;
wpu_phi = 1;
}
asm sleep
}
void InstaPLO()
{
if (PREVSTATE != _insta_plo)
{
STATE = _insta_plo;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
}
asm clrwdt // this clrwdt is absolutely necessary or there will be a wdt timeout every time plo is high
WDTCON = wdt8ms; // since plo is high (liquid has been detected during this pass) shorten sleep and check plo more often than during standby mode
asm sleep
WDTCON = wdt128ms; // return wdt period to at least 32ms-- one loop through void main() at clock = 31khz LFINTOSC takes around 10 to 14ms
}
void InstaPHI()
{
if (PREVSTATE != _insta_phi)
{
STATE = _insta_phi;
OSCCON = osc31khz; // 31kHz LFINTOSC
DisableAudible();
}
asm clrwdt // this clrwdt is absolutely necessary or there will be a wdt timeout every time phi is high
WDTCON = wdt8ms; // since phi is high (liquid has been detected during this pass) shorten sleep and check phi more often than during standby mode
asm sleep
WDTCON = wdt128ms; // return wdt period to at least 32ms-- instructions in void main() during 31khz LFINTOSC take around 10 to 14ms
}
void ImmersedPLO()
{
if (PREVSTATE != _immersed_plo)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _immersed_plo;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_fill;
T1CON = 0b110001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:8, timer1 oscillator off, timer1 on
EnableAudible();
}
}
void ImmersedPHI()
{
if (PREVSTATE != _immersed_phi)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _immersed_phi;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_fill;
T1CON = 0b10001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:2, timer1 oscillator off, timer1 on
EnableAudible();
}
}
void ProbeError()
{
if (PREVSTATE != _probe_error)
{
OSCCON = osc500khz; // 500kHz MFINTOSC
STATE = _probe_error;
INTCON.TMR0IF = 0;
TMR0 = 0;
WDTCON = wdt128ms;
TMR1H = t1h_beep1;
T1CON = 0b100001; // Timer1 clock source is instruction clock (FOSC/4), prescale = 1:4, timer1 oscillator off, timer1 on
EnableAudible();
STATEPERROR = _beep1;
}
}
void Status()
{
if (!PHIlevel)
{
if (!PLOlevel)
{
if (plo) // current pass through comparator has detected liquid at low probe but there is no confirmation of submersion yet (PLOlevel is low), and hi probe not submerged
InstaPLO();
else if (phi)
InstaPHI();
else
Standby();
}
else
ImmersedPLO();
}
else // if (PHIlevel)
{
if (PLOlevel)
ImmersedPHI();
else
ProbeError();
}
PREVSTATE = STATE;
}
void interrupt()
{
if (PIE1.TMR1IE && PIR1.TMR1IF)
{
if (STATE == _probe_error)
{
if (++STATEPERROR > _pause2)
STATEPERROR = _beep1;
switch (STATEPERROR)
{
default:
case _beep1:
BuzzOn();
TMR1H = t1h_beep1;
break;
case _pause1:
BuzzOff();
TMR1H = t1h_pause1;
break;
case _beep2:
BuzzOn();
TMR1H = t1h_beep2;
break;
case _pause2:
BuzzOff();
TMR1H = t1h_pause2;
break;
} // switch
} // if (STATE == _probe_error)
else // if (STATE == _immersed_plo || STATE == _immersed_phi)
{
TMR1H = t1h_fill;
if (buzzing)
BuzzOff();
else
BuzzOn();
}
PIR1.TMR1IF = 0;
} // if (PIE1.TMR1IE && PIR1.TMR1IF)
} // void interrupt()
void main()
{
IniReg();
while(1)
{
if (STATE == _standby) // during standby there is no need to run the debounce routine. Just record the instantaneous value of the probes
{
ReadProbesV1();
PLOval.f0 = plo;
PHIval.f0 = phi;
Status();
}
else
{
if (OSCCON == osc500khz) // this conditional is equivalent to "if (STATE == _immersed_plo || STATE == _immersed_phi || STATE == _probe_error)" since these states run at 500kHz
{
while (!INTCON.TMR0IF) ; // do nothing until one timer0 tick has elapsed
INTCON.TMR0IF = 0;
asm clrwdt
}
ReadProbesV2();
Debounce();
Status();
}
}
}
No comments:
Post a Comment