Tuesday, June 14, 2011

Variable intermittent wiper control - Part 5

Variable intermittent wiper control - Part 4

As broached in Part 4, I'm gaga over a new way (at least to me) of implementing a VIWC. It involves letting the circuit record time elapsed between wipes and button presses to set the time interval for windhshield wipes. As stated this can be accomplished using just one momentary contact switch as user input.

I've already modified the "old" breadboarded potentiometer-based VIWC and bench-tested it to be working. Circuitwise, the only difference between the new more user-friendly VIWC and the former is the replacement of the pot with a momentary contact switch, the addition of a Sonalert buzzer to provide user feedback, and the use of a different MCU--the PIC12F615. The interface with the Innova's wiper module remains unchanged.


I've dumped the 10F222 and chosen a midrange PIC since I'd like to have interrupts to ease my code-writing job and so as to have access to more timers. I always want more timers!

There's really nothing much else to say about the electronics so let's talk about what this improved VIWC does and discuss the firmware.


What the VIWC Does:

Because it's the bedrock on which the intermittent control rests let's first define and discuss the two modes the wiper circuit can be in:
  1. Reset mode -- Entered into by pressing button PB for at least half a second. Upon entering reset mode a short audible alert will sound. In this mode circuit simply records time elapsed since the last wipe. No wipe will be performed until user presses PB again (momentarily or otherwise).
  2. Wipe mode -- Entered into by pressing PB for less than half a second. Upon pressing, a single windshield wipe will be performed and the elapsed time since the last wipe becomes the time interval for all succeeding wipes. The circuit will now automatically sweep the windshield using the recorded time interval until that time PB is pressed again.

Upon power-up a wipe is performed and circuit is put in reset mode. An audible alert (40ms beep followed by 40ms silence followed by a 40ms beep) is issued to inform the driver of this status. No further wipes will be performed until the user presses PB (for less than 0.5s to enter wipe mode or greater than 0.5s to enter reset mode).

When the user presses PB for <0.5s the circuit enters wipe mode. A wipe is immediately performed. The time interval between the last and current wipe is recorded and used as the interval for all succeeding wipes which are automatically performed until that time PB is pressed again.

If the next button press is <0.5s then as before a wipe is immediately performed and the time interval between the last and current wipe is recorded and used as the interval for all succeeding wipes. This implies that momentary button presses (<0.5s) can be used to shorten wipe time intervals but not lengthen them. To do that reset mode is necessary to "teach" the circuit the desired longer time interval.

If the user holds PB down for at least 0.5s the circuit enters reset mode. An audible alert sounds and the circuit will keep recording the time elapsed since the last wipe while waiting for a button press. No further wipes will be performed until PB is pressed.

The following example should make the operation crystal clear.

It's drizzling lightly so the driver switches on the VIWC. Audible alert sounds to inform the driver that reset mode is active. Wiper sweeps the windshield once. After 13 seconds the view through the windshield becomes unduly hindered by the amount of raindrops. The driver presses button PB for a fraction of second. A wipe action is immediately performed. Now every 13 seconds thereafter the windshield is wiped.

Some time later the drizzle weakens, requiring wipe interval to be increased. So the user holds the button down for over half a second. A wipe is performed immediately upon pressing (wipe mode is entered). Half a second after button is pressed the audible alert sounds signalling that reset mode is activated. Some 28 seconds later the driver needs the windshield swept clean. So s/he presses PB momentarily. A wipe is performed and the wipe time interval is set to 28 seconds. The circuit then automatically wipes the windshield every 28 seconds thereafter.

After several minutes the drizzle begins to intensify. After 17 seconds from the last wipe user pushes PB momentarily to clean the windshield. Wiper switches on and time interval is now set to 17 seconds. A couple more minutes pass and the rain intensifies further. 5 seconds after the last wipe the user needs the windshield cleared and so presses button momentarily. Wiper immediately switches on. Circuit will now use a time interval of 5 seconds for succeeding wipes.

In this example the amount of time is explicitly stated only so that circuit operation can be succinctly explained and clearly understood. Needless to say the driver does not count or keep track of the number of seconds from the last wipe before he presses the button.


Firmware Details

All three timers of the 12F615 are used. Nominal timer tick periods given their respective initialized parameters are as follows:

timer0 = 8ms
timer1 = 100ms
timer2 = 50ms

Timer0 tick determines when program execution in main() occurs. Tick of 8ms was chosen for switch debouncing purposes. This is sufficient time to finish functions DebounceSwitch() and ModeCheck().

Timer1 is always on and used to measure elapsed time between wipes. Timer1 interrupt is always enabled.

Timer2 is turned on when a wipe is performed and turned off when wipe is compeleted. It is dedicated to determining how long the wiper motor will be on. Timer2 interrupt is always enabled.

Measurement of wipe time interval: Timer 1 is used to measure wipe time interval. Timer1's 16-bit register and prescaler as well as the 16-bit integer (ELAPSEDTIME) used to record elapsed time are all reset zero whenever wiper is turned on. Therefore, elapsed time (and therefore wipe time interval) is measured from the instant wiper is turned on to the time it is turned on again. The user can set the time interval by pressing the button at two different points in time. However, if measured time between button presses is less than a minimum amount (2 seconds), then firmware will set time interval = 2 seconds

Button is debounced by taking readings every timer0 tick. The instantaneous value is shifted into the LSB of 8-bit register PB.val. The oldest reading is discarded. Switch is deemed not bouncing if the last 8 readings (8 bits) are high or low (PB.val = 0xFF or 0x00). The voltage level of the switch is deemed to be high if all 8 are high and low if all 8 bits are low. When the voltage level changes--high to low or low to high--this is taken as a falling or rising edge.

The reset mode audible alert takes around 120ms to complete and thus temporarily breaks the neat timer0 tick cadence. During this time PB is not read and debounced.

If PB is pressed within 1.5s from the moment wiper was switched on then no wipe action occurs and timer1 and elapsed time counter are not reset. In other words, any and all falling edges detected within that 1.5s period (constant deadtime) are ignored. However, the button hold down time is still monitored and if it exceeds resetmodetime then reset mode is entered.

The various times related to the wiper being turned on are:
Wiper cycle time (measured with stalk switched to INT) = ~1.3s
Wiper on time (determined by constant wipertrigtime) = 1s
PB press dead time (determined by constant deadtime) = 1.5s
Mininum wipe time interval (determined by constant mininterval) = 2s

The relationship between them is wipertrigtime < wiper cycle time < deadtime <= mininterval. The following illustration says it all.

wipertrigtime must of course be < wiper cycle time so that the wiper parks properly. deadtime has to be > wiper cycle time in order that the wiper isn't retriggered before it has parked. And naturally mininterval should be at least equal to deadtime. It doesn't make sense to have it less than deadtime.

Holding down PB for an extended period will merely keep the circuit in reset mode. And the audible alert will keep repeating. It does not reset the elapsed time counter. That only happens if wipe action occurs. And with respect to PB that happens only during a falling edge detect (unless that falling edge occurs within the PB dead time). So the user always knows when elapsed time measurement begins and ends just by noting when the wiper blades move.

/*

Variable Intermittent Wiper Circuit that Learns from User Input
for the Toyota Innova

June 2011
Edwardson Tan

processor = PIC12F615
compiler = mikroC Pro v4.60

*/


#define  buzzer              GPIO.f4             // sonalert buzzer
#define  wiper               GPIO.f5             // 2N7000 switches relay which switches wiper low speed
#define  pb                  GPIO.f2             // momentary contact switch

#define  tris_pb             TRISIO.f2           // used to set pb pin as input
#define  wpu_pb              WPU.f2              // used to enable weak pull up for pb

#define  t0_init_val         256 - 250           // value loaded into TMR0 every time it overflows
#define  t0tick              8                   // timer0 tick time in milliseconds = [(256 - t0_init_val) * prescale / 1MHz] * 1000ms

#define  t1h_init_val        256 - 195           // value loaded into TMR1H every time it rolls over to zero for timer1 to count 0.25sec at a prescale = 1:8 and clock of 4MHz
#define  t1tick              100                 // timer1 tick time in milliseconds = [(256 - t1_init_val) * 256 * prescale / 1MHz] * 1000ms

#define  t2tick              50                  // timer2 tick time in milliseconds = [PR2 * prescale * postscale / 1MHz] * 1000ms

#define  resetmodetime       500/t0tick          // mininum amount of time pb is held down before reset mode is entered. time in terms of timer0 ticks
#define  wipertrigtime       1000/t2tick         // amount of time wiper is turned on. time is in terms of timer2 ticks
#define  deadtime            1500/t1tick         // mininum elapsed time before a button press will trigger a wipe. time is in terms of timer1 ticks
#define  mininterval         2000/t1tick         // mininum time interval between wipes. time is in terms of timer1 ticks

#define  mode                FEN.f0              // 0 = wipe mode,  1 = reset mode
#define  wipe                0
#define  reset               1

#define  rising              1                   // rising edge detected. used by PB.edge
#define  released            1                   // falling edge detected. used by PB.edge
#define  falling             2                   // falling edge detected. used by PB.edge
#define  pressed             2                   // rising edge detected. used by PB.edge
#define  none                0                   // no edge. used by PB.edge

#define  int8                unsigned char
#define  int16               unsigned int
#define  int32               unsigned long

#define  on                  1
#define  off                 0

#define  yes                 1
#define  no                  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  all_bits_hi         0xFF      // for setting an 8-bit register to 255
#define  all_bits_lo         0         // for setting an 8-bit register to 0


// ==================================================================================================================
//       Global Variables
// ==================================================================================================================

struct
{
  val : 8;                             // last eight readings push button. if 0x0 then level is lo, if it is = all_bits_hi then level is hi, any other value means switch is bouncing
  level : 1;                           // level of push button when not bouncing (hi = 1, lo = 0)
  edge : 2;                            // 0 = no edge detect, 1 = rising edge, 2 = falling edge; 3 = Not Used / Undefined for now
}
PB;

int8    PBHELDDOWN;                    // number of timer0 overflows pb has been held down (ie. how long PB.level has been low)
int16   ELAPSEDTIME;                   // elapsed time since the last wipe, in terms of timer1 ticks
int16   WIPEINTERVAL;                  // user selected time interval between wipes, in terms of timer1 ticks
int8    T2COUNTER = 0;                 // stores number of timer2 interrupts that have occured. for use with wiper on time
int8    FEN = 0;                       // Flag and ENable and status bits register
 
// ==================================================================================================================
//       Functions
// ==================================================================================================================

// 1 centisecond = 10 milliseconds = 0.01sec
void DelayCentisec(int8 csec)
{
  int8 i;
  for (i=1; i<=csec; i++)
  {
    Delay_ms(10);
    asm{clrwdt}                        // WDT timeout = 18ms. Therefore it must be cleared here
  }
}

// note: this routine takes over a hundred milliseconds to execute, obviously! it will not finish within the timer0 tick of 8ms
void BeepReset()
{
  buzzer = on;
  DelayCentisec(4);
  buzzer = off;
  DelayCentisec(4);
  buzzer = on;
  DelayCentisec(4);
  buzzer = off;
}

// wiper is turned on
// timer2 is turned on
// timer1 registers and prescaler and 16-bit variable that records elapsed time since last wipe are all reset to zero
// note: wiper will be turned off in the ISR
void Wipe()
{
  wiper = on;
  T2CON.TMR2ON = on;
  TMR1L = 0;
  TMR1H = t1h_init_val;
  ELAPSEDTIME = 0;
}

void InitRegisters()
{
  // I/O pins
  // all are output except for pb, weak pull enabled for pb, all I/Os are digital, clear output pins
  GPIO = 0;
  ANSEL = digital;
  TRISIO = output;
  tris_pb = input;
  wpu_pb = on;

  // timer0 registers
  // given 4MHz clock, prescale = 1:32, TMR0 init val = 6
  // timer0 tick = (256 - 6)(32)(1us) = 8.00ms
  // TMR0 doesn't need to be initialized to any value since the first wipe action will take one second (firmware waits until this is wipe is over) and by then T0IF will be set whatever TMR0 initial value was
  OPTION_REG = 0b100;                  // global weak pull ups on, prescaler assigned to timer0, prescale = 1:32

  // timer1 registers
  // given 4MHz clock, prescale = 1:2, TMR1H init val = 61
  // timer1 tick = (256 - 61)(256)(2)(1us) = 99.84ms
  // upon power up timer1 runs continuously; it is never stopped, but TMR1L and TMR1H are reinitialized upon button press
  T1CON = 0b010001;                    // prescale = 1:2, timer1 source = internal clock, timer1 on
  TMR1L = 0;
  PIE1.TMR1IE = on;

  // timer2 registers
  // given 4MHz clock, prescale = 1:16, postscale = 1:16, PR2 = 195
  // timer2 tick = (195)(16)(16)(1us) = 49.92ms
  // timer2 dedicated to
  T2CON = 0b1111011;                   // postscale = 1:16, timer2 off, prescale = 1:16
  PR2 = 195;                           // when timer2 running TMR2 will increment until it matches PR2 and then TMR2 will be reset
  TMR2 = 0;
  PIE1.TMR2IE = on;

  // initialize to no-button-press condition
  PB.val = all_bits_hi;
  PB.level = hi;
  PB.edge = none;
  
  mode = reset;                        // circuit starts up in reset mode, i.e., wiper will sweep windshield once upon power up and then wait for user to trigger wiper to lock in wipe time interval
  WIPEINTERVAL = mininterval;

  INTCON.GIE = on;
  INTCON.PEIE = on;
}

// The following debounce and edge and level detect routine is performed every timer0 tick
void DebounceSwitch()
{
  // shift all bits to the left
  // if switch reading is hi then PB.val bit0 gets set
  PB.val <<= 1;
  if (pb)
    ++PB.val;

  // if pb has been pressed then start counting how long it is kept pressed
  // a key is said to be pressed when level is lo
  // if level is high then reset counter
  if (!PB.level)
    ++PBHELDDOWN;
  else
    PBHELDDOWN = 0;

  // assume no edge detected
  PB.edge = none;
  
  // if level is lo and all bits of PB.val are now hi then
  // a rising edge has been detected
  // switch is considered just released when rising edge is detected
  // switch level is now hi
  if ((!PB.level) && (PB.val == all_bits_hi))
  {
    PB.level = hi;
    PB.edge = rising;
  }

  // if level is hi and all bits of PB.val are now low then
  // a falling edge has been detected
  // switch is considered just pressed when falling edge is detected
  // switch level is now lo
  if ((PB.level) && (PB.val == 0))
  {
    PB.level = lo;
    PB.edge = falling;
  }
} // void DebounceSwitch()


// This function will check if pb has been pressed
// If pressed then wipe action will be issued and wipe mode is entered
// However, button press--falling edge dected--is ignored if it occurs less than deadtime from the last wipe
// if button is held down > for a time greater than resetmodetime then reset mode is entered.
// note: ELAPSEDTIME is reset to zero in the function Wipe()
void ModeCheck()
{
  if (PB.edge == falling && ELAPSEDTIME >= deadtime)
  {
    WIPEINTERVAL = ELAPSEDTIME;
    if (WIPEINTERVAL < mininterval)
      WIPEINTERVAL = mininterval;
    mode = wipe;
    Wipe();
  }
  else if (PBHELDDOWN >= resetmodetime)
  {
    mode = reset;
    PBHELDDOWN = 0;
    BeepReset();
  }
}

void interrupt()
{
  if (PIR1.TMR1IF)
  {
    TMR1H = t1h_init_val;
    PIR1.TMR1IF = off;
    ++ELAPSEDTIME;
    if ((mode == wipe) && (ELAPSEDTIME >= WIPEINTERVAL))    // if time interval has already been entered by user, then wipe action will automatically be performed after every time interval
      Wipe();
  } // if (PIR1.TMR1IF)
  
  if (PIR1.TMR2IF)
  {
    PIR1.TMR2IF = 0;
    if (++T2COUNTER >= wipertrigtime)
    {
      wiper = off;
      T2CON.TMR2ON = off;
      T2COUNTER = 0;
      TMR2 = 0;
    }
  } // if (PIR1.TMR2IF)
} // void interrupt()


// ==================================================================================================================
//       main
// ==================================================================================================================

void main()
{
  InitRegisters();
  Wipe();
  BeepReset();
  // while(wiper);              // wait till wipe has finished before proceeding, so that any pb press will have no effect
  
  while(1)
  {
    if (INTCON.T0IF)
    {
      asm{clrwdt}
      INTCON.T0IF = off;
      TMR0 = t0_init_val;
      DebounceSwitch();
      ModeCheck();
    } // if (INTCON.T0IF)
  } // while(1)
} // void main()


Testing the VIWC

To see if the circuit and firmware are really working as expected I hooked up the Saleae logic analyzer to the PIC12F615's GP2 pin (PB) and GP5 (Q1 gate). In the text Q1 was driving only an LED not REL2. A really small tactile switch was used for PB. Circuit was powered via PICkit2 set to 5.0V. In all of the screenshots below channel 6 is hooked up to GP5 and channel 7 to GP2.

I wish I could've set the sampling rate to 1MHz and higher but my decade-old computer only has USB1.0 which limits the Saleae to 500kHz. Not infrequently it can't even maintain that and I've experienced the rate going down to 200kHz.

In the first screen capture below the green vertical cursors 1 and 2 have been positioned at the falling edge of GP2 and rising edge of GP5. You can see that |T1 - T2| = 62.6ms. This delay between PB press and wiper switch on is as expected due to the switch debouncing routine which requires all 8 bits of PB.val to be either high or low before a level change is said to have occurred. Those eight bits take eight timer0 ticks. At 8ms, we would expect a nominal 64ms from the actual falling edge of GP2 to the moment wiper is switched on.

Take a look at the amount of time GP5 is high. The pulse width is almost exactly 1 second as we have specified. Note the second falling edge of GP2--PB was pressed again momentarily--causing a wipe action to occur and setting the wipe interval = 4 seconds (read the item labeled "Period")


Zooming in and measuring how long PB was pressed we see that the pulse width is almost 120ms.
 

The shot below shows the wipe interval of succeeding wipes is still 4seconds (3.98s in the instance below). Keep in mind that timer1 tick = 100ms so that's the tolerance value. 


I intentionally waited for the LED to light and then pressed PB rapidly. Remember we specified deadtime = 1.5s so a falling edge on or after an elapsed time of 1.5sec after GP5 goes high should be recognized. Well apparently this did not happen as the shot below shows. Wiper was turned on only after some 1.92s. Is the firmware buggy?


After closer inspection it turns out that I was pressing PB at such a rate that some presses (level low) and nonpresses (level high) were shorter than the required debouncing period of 64ms. Take a look at T1-T2 where cursor 2 is at a falling edge of PB. It's 1.7s. Wiper ought to have been turned on some 64ms after this edge. But then look at the pulse width (rising edge to falling edge). The pulse was high for only 49ms. Because not all bits were high the firmware took this as indicative of switch contacts that are bouncing and thus no valid level change (edge detect) was recognized.


The next button release and button press, on the other hand, met the minimum debouncing requirements: high time was 87ms while low time = 203ms - 87ms = 116ms. Hence wiper was switched on after the valid falling edge was recognized.



Recall that the measured wipe interval was 1.92s. This is less than the minimum of 2s we specified. Thus for the succeeding wipes, firmware set the wipe interval to 2s, with actual period measured to be 1.99s.



The next screenshots are from a different set of data capture. In this first image time between GP2 falling edge and GP5 rising edge is 60ms. The wipe interval is a mere 1.52ms--this violates the minimum and so we should see it changed to 2s in the next wipe (if no button presses occur within that interval).


I moved the cursors to measure the the elapsed time from the moment wiper was turned on to the moment when the falling edge which triggers the wiper occurs. T1-T2 = 1.46s. Doesn't this violate the 1.5s deadtime requirement? No it doesn't. Remember that the debouncing routine needs 64ms to "see" an actual falling edge. So the nominal dead time between wiper turn on and actual (in contrast to firmware determined) falling edge = 1.5s - 64ms = ~1.44s 


As expected in the very next wipe the time interval has been adjusted to conform to the minimum of 2s. Actual measured period = 1.99s.


Here I held down PB while GP5 was still high (LED still on). Pulse width (time PB was held down) = 1.05s. Having pressed PB during the dead time, no wipe occurred. But because PB was pressed for >0.5s, reset mode became active--as can be seen no wipe occurred after 2s (the wipe time interval before the reset). Wiper turns on again only after PB was pressed again.


These last two shots show that elapsed time / wipe interval is measured from the moment wiper is switched on (GP5 rising edge) and not when PB is pressed. As you can see the period is 4.18s in both cases even if PB was pressed (to enter reset mode) almost a second after wiper was turned on.




So there you have it. A more or less full documentation of what for me is an exciting implementation of a VIWC. I'm not rushing to fabricate a PCB for it though. I feel there's a need to have an extended pilot test to get a feel for how well it works and to see if there's anything that needs to modified, tweaked, revised. So I'm stashing the prototype and relays and the whole tangle of wires under the dashboard and installing temporary switches on the lower part of the dash for now. And obviously I'll be doing daily rain dances for the next two weeks.


[June 14 edit: I commented out the line "while(wiper);" in main(). That line is unnecessary]

[June 16 edit: Forgot to mention the maximum wipe time interval possible. Given timer1 tick = 0.1s and ELAPSEDTIME being a 16--bit variable, the maximum time the variable can measure before overflowing back to zero = 65,535 * 0.1s = 6553.5s or ~109 minutes. I'm sure! that's sufficient for lightest of drizzles.]

No comments:

Post a Comment