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