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 functionsInstaPLO()
,InstaPHI()
, andDisableAudible
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 ofif
statements. So in the ISR the probe error routine now usesif / 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. Withif / 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
/* 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