speedo.c


1
/*
2
speedometer for PIC16F648A
3
Copyright 2007 by Jeff Hiner
4
version: 0.2, 21 May 2008
5
6
This program is free software: you can redistribute it and/or modify
7
    it under the terms of the GNU General Public License as published by
8
    the Free Software Foundation, either version 3 of the License, or
9
    (at your option) any later version.
10
11
    This program is distributed in the hope that it will be useful,
12
    but WITHOUT ANY WARRANTY; without even the implied warranty of
13
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
    GNU General Public License for more details.
15
16
    You should have received a copy of the GNU General Public License
17
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19
20
21
This program should compile under SourceBoost.  Feel free to modify it
22
to compile with any program you wish, for any platform you wish.
23
24
KNOWN BUGS:
25
26
Decreases in speed register immediately and are not affected by averaging.
27
This is deliberate; otherwise, speed decreases are not registered quickly
28
and accurately.  However, this can cause the display to oscillate rapidly
29
between two speeds, for example 38 and 40.
30
31
32
The most recent version can be found at:
33
http://www.randomwisdom.com/2007/10/digital-speedometer-using-pic-microcontroller/
34
35
*/
36
37
#include <system.h>
38
#define _DEBUG
39
40
#pragma DATA _CONFIG, _LVP_OFF & _BOREN_ON & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _INTOSC_OSC_NOCLKOUT
41
42
// This macro translates the order of the 7-segment bits to whatever the hardware pins are actually assigned to.
43
// input is from H to A, H being the decimal and A being the top LED, etc (see 7 segment documentation)
44
// This lets us change the wiring without altering all the bitwise code.
45
#define SEVSEG_BITFIELD(H,G,F,E,D,C,B,A)  0b ## E ## A ## D ## G ## F ## C ## B ## H
46
// mask the SEVSEG_BITFIELD with the following to decide what bits get written to when writing out to display.
47
#define SEVSEG_PORTA_MASK          0b11001110
48
#define SEVSEG_PORTB_MASK          0b00110000
49
#define SEVSEG_DECIMAL_MASK          SEVSEG_BITFIELD(1,0,0,0,0,0,0,0)
50
#define SEVSEG_DECIMAL_BIT          portb.1
51
#define SPEEDO_INPUT            portb.3
52
#define  BUTTON_INPUT            porta.5
53
#define hundreds_driver            portb.7
54
#define tens_driver             portb.6
55
#define ones_driver              portb.0
56
#define CALIBRATION_SPEED_KPH        50
57
#define  DEFAULT_CALIB_FACTOR        48094
58
#define DEFAULT_PULSES_IN_QTRMILE      941
59
60
61
// how many overflows of tmr1 before a pulse is considered "stale" (invalid)
62
#define PULSE_LIFETIME      7
63
#define MENU_TIMEOUT      12
64
// 62 bigticks = 1.98ms
65
#define SPEEDO_BOUNCE_THRESHOLD  62
66
67
typedef struct _uns24 {
68
  unsigned char low8;
69
  unsigned char mid8;
70
  unsigned char high8;
71
} uns24;
72
73
#define gie        intcon.GIE
74
#define t0if      intcon.T0IF
75
#define tmr1if      pir1.TMR1IF
76
#define tmr2if      pir1.TMR2IF
77
#define ccp1if      pir1.CCP1IF
78
79
unsigned char sevenseg_lookup(unsigned char digit);
80
void sevenseg_text(unsigned char message);
81
void refresh_sevenseg();
82
void display_number(unsigned int num);
83
void calculate_speed(bit forceupdate);
84
unsigned int make16(uns24 bigtimer);
85
unsigned int make16i(uns24 bigtimer);  // same thing, just used only in interrupts
86
unsigned long makelong(uns24 bigtimer);
87
unsigned char eeprom_read(unsigned char address);
88
void eeprom_write(unsigned char address, unsigned char data);
89
90
// The display unit's current task is defined by a state machine.  Normally on boot it defaults to MODE_SPD
91
// which displays the current speed as a normal speedometer.
92
// MSG_EEE is not a real mode, but it's here to prevent an overflow bug in sevenseg_text
93
typedef enum { MODE_MENU_DRAG, MODE_MENU_CAL, MODE_MENU_TEST, MSG_GO, MSG_EEE,
94
          MODE_SPD, MODE_DRAG, MODE_CAL, MODE_TEST, MODE_TEST2, MODE_TEST3 } mainmode;
95
96
#define MODE_DEFAULT  MODE_SPD
97
98
// max eeprom space: 256 bytes
99
#define eeprom_calib_factor_kph_lo  0
100
#define eeprom_calib_factor_kph_hi  1
101
#define  eeprom_calib_factor_mph_lo  2
102
#define eeprom_calib_factor_mph_hi  3
103
#define eeprom_pulses_in_qtrmile_lo  4
104
#define eeprom_pulses_in_qtrmile_hi  5
105
#define eeprom_options        6
106
  // bit 0 is 0 if displaying mph, 1 if displaying kph.  NOT YET IMPLEMENTED.
107
  // bit 7 is 0 if this eeprom has ever been written to, 1 if cleanly erased (load all from defaults)
108
  // all others are undefined
109
110
// max ram space: 256 bytes
111
unsigned int calib_factor;      // calibration factor used to calculate speed
112
unsigned int pulses_in_qtrmile;    // we use an actual quarter mile: about 402.25 meters
113
unsigned char sevenseg_bits[3];    // a buffer telling which LEDs we light up on each digit
114
unsigned int speed;          // our current speed, calculated in main loop (unsigned char only goes to 255)
115
unsigned int pulsecount;      // updated on each pulse.  Used for drag timing.
116
uns24 lastpulses[2];        // the last two pulses received.  index 0 is last, index 1 is the one before that.
117
bit lastpulse_stale0;
118
bit lastpulse_stale1;        // if a pulse happened too long ago, it's stale and we can't do anything with it
119
bit blink_enable;          // set by software: blinking enabled on all displays
120
bit flag_buttonpressed;        // used to pass the message that a button press was registered
121
bit flag_buttonheld;        // ... or a button was held.  Clear manually before and after use.
122
123
unsigned char button_heldcount;    // how many tmr0 ticks the button has been held
124
unsigned char dim_factor;      // inserts (0xFF - dim_factor) delays in the 7seg refresh to dim the display.
125
                  // if 0xFF, we don't dim displays at all.  If less than that, we dim.
126
                  // (e.g. 0xFC for half power, 0xF9 for 1/3 power, etc)
127
mainmode current_mode;        // tells our state machine which task the main loop is trying to execute
128
unsigned char tmr1_upper;      // since 0.5 seconds isn't long enough, we increment this with tmr1 overflows
129
                  //   and use it for top byte of lastpulses.
130
uns24 now;              // last sampled tmr1/tmr1_upper "now" time.
131
                  //   "When will then be now?"  "Soon!"
132
133
// converts smalltimer "ticks" to tenths of a second
134
// (one smalltimer tick is 8 microseconds)
135
inline unsigned int to_millisecs(unsigned long bigtimer)
136
{
137
  return (unsigned int)((bigtimer + 6250) / 12500); // let's at least try to round it
138
}
139
140
// Looks up digit in the lookup table, then returns the 7 segment bits.
141
// These shouldn't need to be changed; if your wiring is different, change the SEVSEG macros at the start.
142
unsigned char sevenseg_lookup(unsigned char digit)
143
{
144
  rom unsigned char* lookuptable = {SEVSEG_BITFIELD(0,0,1,1,1,1,1,1), SEVSEG_BITFIELD(0,0,0,0,0,1,1,0),
145
              SEVSEG_BITFIELD(0,1,0,1,1,0,1,1), SEVSEG_BITFIELD(0,1,0,0,1,1,1,1),
146
              SEVSEG_BITFIELD(0,1,1,0,0,1,1,0), SEVSEG_BITFIELD(0,1,1,0,1,1,0,1),
147
              SEVSEG_BITFIELD(0,1,1,1,1,1,0,1), SEVSEG_BITFIELD(0,0,0,0,0,1,1,1),
148
              SEVSEG_BITFIELD(0,1,1,1,1,1,1,1), SEVSEG_BITFIELD(0,1,1,0,1,1,1,1)  };
149
150
  if (digit > 9)
151
    return 0;
152
  return lookuptable[digit];
153
}
154
155
// writes "text" on the 7 segment display by setting BCD position bits
156
void sevenseg_text(unsigned char message)
157
{
158
  gie = 0;
159
  // block interrupts while we're writing sevenseg_bits
160
161
  switch (message)
162
  {
163
    case MODE_MENU_DRAG:  // dragstrip mode (drG)
164
      sevenseg_bits[2] = SEVSEG_BITFIELD(0,1,0,1,1,1,1,0);
165
      sevenseg_bits[1] = SEVSEG_BITFIELD(0,1,0,1,0,0,0,0);
166
      sevenseg_bits[0] = SEVSEG_BITFIELD(0,0,1,1,1,1,0,1);
167
      break;
168
    case MODE_MENU_CAL:    // calibration (CAL)
169
      sevenseg_bits[2] = SEVSEG_BITFIELD(0,0,1,1,1,0,0,1);
170
      sevenseg_bits[1] = SEVSEG_BITFIELD(0,1,1,1,0,1,1,1);
171
      sevenseg_bits[0] = SEVSEG_BITFIELD(0,0,1,1,1,0,0,0);
172
      break;
173
    case MODE_MENU_TEST:  // test mode (tSt)
174
      sevenseg_bits[2] = SEVSEG_BITFIELD(0,1,1,1,1,0,0,0);
175
      sevenseg_bits[1] = SEVSEG_BITFIELD(0,1,1,0,1,1,0,1);
176
      sevenseg_bits[0] = SEVSEG_BITFIELD(0,1,1,1,1,0,0,0);
177
      break;
178
    case MSG_GO:      // during drag (Go!)
179
      sevenseg_bits[2] = SEVSEG_BITFIELD(0,0,1,1,1,1,0,1);
180
      sevenseg_bits[1] = SEVSEG_BITFIELD(0,1,0,1,1,1,0,0);
181
      sevenseg_bits[0] = SEVSEG_BITFIELD(1,0,0,0,0,0,1,0);
182
      break;
183
    case MSG_EEE:      // error (EEE)
184
      sevenseg_bits[2] = SEVSEG_BITFIELD(0,1,1,1,1,0,0,1);
185
      sevenseg_bits[1] = SEVSEG_BITFIELD(0,1,1,1,1,0,0,1);
186
      sevenseg_bits[0] = SEVSEG_BITFIELD(0,1,1,1,1,0,0,1);
187
      break;
188
  }
189
  gie = 1;
190
}
191
192
// refreshes a single seven segment element, and increments the digit
193
// This func is run a lot, so it must be fast!
194
void refresh_sevenseg()
195
{
196
  static unsigned char current_digit;
197
198
  // turn off all digit driver transistors
199
  ones_driver = 0;
200
  tens_driver = 0;
201
  hundreds_driver = 0;
202
203
  porta &= ~(SEVSEG_PORTA_MASK);
204
  portb &= ~(SEVSEG_PORTB_MASK);
205
  SEVSEG_DECIMAL_BIT = 0;
206
207
  if ((!blink_enable) || (tmr1h.7))
208
  {
209
    // assign proper bits to the ports
210
    porta |= (SEVSEG_PORTA_MASK & sevenseg_bits[current_digit]);
211
    portb |= (SEVSEG_PORTB_MASK & sevenseg_bits[current_digit]);
212
    if (sevenseg_bits[current_digit] & SEVSEG_DECIMAL_MASK)
213
      SEVSEG_DECIMAL_BIT = 1;  // don't forget decimal
214
215
    // turn on the current digit driver transistor
216
    switch (current_digit)
217
    {
218
      case 0:
219
        ones_driver = 1;
220
        break;
221
      case 1:
222
        tens_driver = 1;
223
        break;
224
      case 2:
225
        hundreds_driver = 1;
226
        // normally dim_factor is 0xFF, which means start again at case 0 next pass.
227
        // if less, we skip lighting up for 0xFF - dim_factor passes.
228
        // A hack, because current_digit maybe isn't a legal array index...
229
        current_digit = dim_factor;
230
        break;
231
    }
232
  }
233
234
  // next pass, we'll update this digit
235
  current_digit++;
236
}
237
238
// translate "num" to BCD.  Blank leading zeroes, but if num is zero show 0 in ones digit.
239
void display_number(unsigned int num)
240
{
241
  // If you're going faster than 512, send me a video.  I'd like to see it.
242
  if (num > 512)
243
  {
244
    sevenseg_text(MSG_EEE);
245
    return;
246
  }
247
  else
248
  {
249
    unsigned char temp_digit[3];
250
251
    // we need to BCD decode a 9 bit number.
252
253
    // normally I'd use a for loop, but 8 bit division is much faster than 16 bit and we need the speed.
254
    temp_digit[0] = num % 10;
255
    unsigned char temp_num = num / 10;
256
    temp_digit[1] = temp_num % 10;
257
    temp_digit[2] = temp_num / 10;
258
259
    gie = 0;
260
    sevenseg_bits[2] = 0x00;
261
    sevenseg_bits[1] = 0x00;
262
263
    if (temp_digit[2] != 0)
264
    {
265
      sevenseg_bits[2] = sevenseg_lookup(temp_digit[2]);
266
      sevenseg_bits[1] = sevenseg_lookup(temp_digit[1]);
267
    }
268
    else if (temp_digit[1] != 0)
269
    {
270
      sevenseg_bits[1] = sevenseg_lookup(temp_digit[1]);
271
    }
272
    sevenseg_bits[0] = sevenseg_lookup(temp_digit[0]);
273
    gie = 1;
274
275
    // Faster way?  Save the answer and remainder simultaneously, only compute as much as we need.
276
    // But it would need to be implemented in asm.
277
  }
278
}
279
280
// calculate our speed and write it to the "speed" variable
281
// "forceupdate" means ignore hysteresis and return raw speed
282
void calculate_speed(bit forceupdate)
283
{
284
  unsigned int twopulse_delta, lastpulse_delta, temp_lastpulses[2];
285
286
  static unsigned char lastupdatedtime = 0;
287
  static unsigned int rollingsum[6] = {0, 0, 0, 0, 0, 0};  // a rolling sum of the last 2, 4, 8, 16, 32, and 64 pulseswidths (estimated)
288
  static unsigned char lastpulsecount = 0;    // the last known pulsecount, based on least significant 8 bits
289
                          // (only recalculate speed if we have another pulse)
290
291
  int newspeed;  // signed int
292
293
  // if we don't have two valid pulses, we're done. Speed is zero.
294
  if (lastpulse_stale1 || lastpulse_stale0)
295
  {
296
    unsigned char i;
297
    for (i = 0; i < 6; i++)
298
      rollingsum[i] = 0;  // clear all rolling sums
299
    speed = 0;
300
    return;
301
  }
302
303
  SampleNow:
304
  // stop interrupts for a sec... we don't want the clocks messed with
305
  gie = 0;
306
307
  // This time, we cannot stop captures, so we should check consistency.
308
  now.mid8 = tmr1h;
309
  now.high8 = tmr1_upper;
310
  now.low8 = tmr1l;
311
312
  // if mid8 has changed, then low8 overflowed and high8 is also suspect.
313
  if (now.mid8 != tmr1h)
314
  {
315
    if (tmr1if)
316
    {
317
      // one in a million chance... tmr1 overflowed! oh noes!
318
      // tmr1_upper hasn't had a chance to update. We need to enable interrupts, go back, and try again.
319
      gie = 1;
320
      goto SampleNow;
321
    }
322
    // if we copy again, it should be fast enough to not overflow this time.
323
    now.mid8 = tmr1h;
324
    now.high8 = tmr1_upper;
325
    now.low8 = tmr1l;
326
  }
327
328
  temp_lastpulses[1] = make16(lastpulses[1]);
329
  temp_lastpulses[0] = make16(lastpulses[0]);
330
  // start the interrupts again
331
  gie = 1;
332
333
  // now.low16 = make16(now);
334
  lastpulse_delta = make16(now);
335
336
  // calculate difference between last 2 pulses' clock
337
  twopulse_delta = temp_lastpulses[0] - temp_lastpulses[1];
338
  // calculate difference between last pulse and now
339
  // lastpulse_delta = now.low16 - temp_lastpulses[0];
340
  lastpulse_delta -= temp_lastpulses[0];
341
342
  if (lastpulsecount != (unsigned char)pulsecount)  // NOTE: only update rolling average of times between two pulses
343
  {
344
    // we have a new pulse so we can update antijitter
345
    lastpulsecount = (unsigned char)pulsecount;
346
    // rollingsum[n] = ((2^n - 1) * rollingsum[n] / (2^n)) + (twopulse_delta)
347
    unsigned char i;
348
    for (i = 0; i < 6; i++)
349
    {
350
      rollingsum[i] -= (rollingsum[i] >> (i+1));
351
      rollingsum[i] += twopulse_delta;
352
    }
353
  }
354
355
  // whichever of those is greater, call that "period"
356
  // speed is calibfactor / period
357
  if (twopulse_delta > lastpulse_delta)
358
    newspeed = calib_factor / twopulse_delta;
359
  else if (lastpulse_delta == 0) // if (lastpulse_delta==0) && (twopulse_delta==0) catch the edge case so we don't divide by zero
360
    newspeed = 0;
361
  else  // in this case, our speed is lower because we haven't got a pulse yet as expected...
362
    newspeed = calib_factor / lastpulse_delta;
363
364
  // n = calib_factor / (twopulse_delta * 10)
365
366
  unsigned char n = (unsigned int)newspeed / 10;
367
368
  if (n)  // if going faster than 10kph, use an appropriate rolling average
369
  {
370
    unsigned char i = 0;  // "i" will tell us how many speed estimates to blend (2 for 10kph, 4 for 20, 8 for 40, and so on)
371
    while (n)
372
    {
373
      n = n >> 1;
374
      i++;
375
    }
376
    unsigned int sumpulse = (rollingsum[i-1] >> i);
377
    if (sumpulse)  // if (rollingsum[i-1] >> i) gets glitched to be zero, we'd be in serious trouble
378
      newspeed = calib_factor / sumpulse;
379
  }
380
381
  int t = (int)newspeed - speed;
382
  // we don't want to update the display if it hasn't changed much and it was already updated recently (within the last 0.25sec)
383
  if ((2 > t) && (t > -2) && (lastupdatedtime == (tmr1h & 0x80)) && !forceupdate)
384
    return;
385
  else
386
  {
387
    lastupdatedtime = tmr1h & 0x80;
388
    speed = (unsigned int)newspeed;
389
  }
390
}
391
392
// turns a 24 bit "long timer" entry into a 16 bit "short timer"
393
// equivalent to (word)(bigtimer >> 2)
394
unsigned int make16(uns24 bigtimer)
395
{
396
  unsigned int rval;
397
  // roll high8, mid8, low8; do it twice
398
  asm {
399
    rrf _bigtimer+2, 1
400
    rrf _bigtimer+1, 1
401
    rrf _bigtimer, 1
402
    rrf _bigtimer+2, 1
403
    rrf _bigtimer+1, 1
404
    rrf _bigtimer, 1
405
  }
406
  MAKESHORT(rval, bigtimer, bigtimer+1);
407
  return rval;
408
}
409
410
// turns a 24 bit "long timer" entry into a 16 bit "short timer"
411
// (Use this one inside interrupts to avoid static memory limitations)
412
unsigned int make16i(uns24 bigtimer)
413
{
414
  unsigned int rval;
415
  asm {
416
    rrf _bigtimer+2, 1
417
    rrf _bigtimer+1, 1
418
    rrf _bigtimer, 1
419
    rrf _bigtimer+2, 1
420
    rrf _bigtimer+1, 1
421
    rrf _bigtimer, 1
422
  }
423
  MAKESHORT(rval, bigtimer, bigtimer+1);
424
  return rval;
425
}
426
427
// synthesize an "unsigned long" from an "uns24" (cc5x style 24 bit unsigned int)
428
unsigned long makelong(uns24 bigtimer)
429
{
430
  unsigned long foo;
431
/*  *((unsigned char *)(&foo)) = 0;
432
  *((unsigned char *)(&foo) + 1) = bigtimer.high8;
433
  *((unsigned char *)(&foo) + 2) = bigtimer.mid8;
434
  *((unsigned char *)(&foo) + 3) = bigtimer.low8;
435
*/
436
  asm
437
  {
438
    movlw 0
439
    movwf _foo+3
440
    movf _bigtimer+2,0
441
    movwf _foo+2
442
    movf _bigtimer+1,0
443
    movwf _foo+1
444
    movf _bigtimer,0
445
    movwf _foo
446
  }
447
  return foo;
448
}
449
450
void main(void)
451
{
452
  // initialization of ports, timers, interrupts, variables, etc
453
  cmcon = 0b00000111;
454
  trisa = 0b00110001;    // 1 means high impedance (input)
455
  trisb = 0b00001100;
456
  porta = 0b00010000;    // 0 is low (gnd), 1 is high (5V)
457
  portb = 0b00000000;
458
459
460
  clear_wdt();  // do this to avoid a reset
461
  option_reg = 0b11010110;  // tmr0 triggers off internal clock, tmr0 prescaler to 1:128
462
  tmr0 = 0x00;      // in case we want to use it later, it's reset.
463
464
  dim_factor = 0xFF;
465
  {
466
    unsigned char temp1, temp2;
467
    temp1 = eeprom_read(eeprom_calib_factor_kph_lo);
468
    temp2 = eeprom_read(eeprom_calib_factor_kph_hi);
469
    MAKESHORT(calib_factor, temp1, temp2);
470
    temp1 = eeprom_read(eeprom_pulses_in_qtrmile_lo);
471
    temp2 = eeprom_read(eeprom_pulses_in_qtrmile_lo);
472
    MAKESHORT(pulses_in_qtrmile, temp1, temp2);
473
  }
474
  if (calib_factor == 0xFFFF || pulses_in_qtrmile == 0)
475
  {
476
    calib_factor = DEFAULT_CALIB_FACTOR;
477
    pulses_in_qtrmile = DEFAULT_PULSES_IN_QTRMILE;
478
  }
479
  lastpulse_stale0 = 1;  // both pulses are invalid on reset; we need to get TWO pulses in a reasonable time
480
  lastpulse_stale1 = 1;  // before doing any calculating.
481
  button_heldcount = 0;
482
  blink_enable = 0;
483
  current_mode = MODE_DEFAULT;  // can set MODE_DEFAULT to MODE_TEST for PIC testing
484
  sevenseg_bits[2] = sevenseg_bits[1] = sevenseg_bits[0] = 0xFF;
485
  now.low8 = 0;
486
  now.mid8 = 0;
487
  now.high8 = 0;
488
489
  tmr1l = 0x00;      // reset tmr1
490
  tmr1h = 0x00;
491
  tmr1_upper = 0;
492
  ccpr1l = 0x00;      // and CCP cap timer
493
  ccpr1h = 0x00;
494
  t1con = 0b00110001;    // ===Start tmr1=== with 1:8 prescale; 125000 increments per second
495
  ccp1con = 0b00000100;  // CCP capture mode ON, every falling edge of RB3 we capture the contents of tmr1 into CCPR1
496
497
  // tweak pr2 or t2con postscaler to adjust LED refresh rate; too high a refresh means less time for speed calcs
498
  tmr2 = 0x00;      // reset tmr2
499
  pr2 = 125;        // wrap when you hit this number
500
  t2con = 0b00001101;  // ===Start tmr2=== with prescaler 1:4, postscaler 1:2 (pr2 = 125 means interrupt every 1ms)
501
502
503
  pir1 = 0b00000000;  // clear the rest of the interrupt flags
504
  pie1 = 0b00000011;  // enable interrupts: tmr1, tmr2
505
  intcon = 0b01100000;  // enable interrupts: tmr0 and PEIE
506
  clear_wdt();
507
  gie = 1;        // enable global interrupts, now the display can start updating
508
509
  // set all LEDs on (lamp test mode) for about 1 second
510
511
  while (tmr1_upper < 2)
512
  {
513
    sevenseg_bits[2] = sevenseg_bits[1] = sevenseg_bits[0] = 0xFF;
514
  }
515
  clear_wdt();
516
517
  // set all LEDs off (lamp blank mode) for about 0.5 seconds
518
  while (tmr1_upper < 3)
519
  {
520
    sevenseg_bits[2] = sevenseg_bits[1] = sevenseg_bits[0] = 0x00;
521
  }
522
  clear_wdt();
523
524
  // by now we should have collected enough pulses (or not collected any) to get an idea of how fast we're going
525
526
  while (1)
527
  {
528
    uns24 start_time, end_time;
529
    unsigned char gen_timer;  // a general variable we can set to tmr1_upper + whatever, and break when they match. Simple software timer.
530
531
    flag_buttonpressed = 0;
532
    flag_buttonheld = 0;
533
    blink_enable = 0;  // just in case we broke out and didn't switch it off
534
535
    // When control enters a new state, the button flags are cleared, blink is turned off.
536
    // Display bits are however the last state left them.  Handy for blinking our selection when exiting a menu.
537
538
    switch (current_mode)
539
    {
540
      case MODE_SPD:  // calculate and show current speed until a button is held
541
        while (!flag_buttonheld)
542
        {
543
          calculate_speed(0);
544
          display_number(speed);
545
        }
546
        // switch to first menu state
547
        current_mode = MODE_MENU_DRAG;
548
        break;
549
      case MODE_CAL:      // calibration mode
550
        // user should be driving 50kph
551
        // flash CAL a couple times for a couple seconds to let the driver get ready
552
        gen_timer = tmr1_upper + 4;
553
        blink_enable = 1;
554
        while (tmr1_upper != gen_timer)
555
          ;  // delay
556
557
        // wait for first pulse
558
        pulsecount = 0;
559
        gen_timer = tmr1_upper + 10;
560
        while (pulsecount == 0 && tmr1_upper != gen_timer)
561
        {
562
          if (flag_buttonpressed || flag_buttonheld)
563
          {
564
            current_mode = MODE_SPD;
565
            break;
566
          }
567
        }
568
569
        if (pulsecount == 0)
570
        {
571
          current_mode = MODE_SPD;
572
          break;  // a button was pressed before the first pulse was received, abort MODE_CAL.
573
        }
574
575
        // blank the display except blinking decimals while reading/calibrating
576
        sevenseg_bits[2] = sevenseg_bits[1] = sevenseg_bits[0] = SEVSEG_DECIMAL_MASK;
577
578
        // log time, handily "now" was just updated by CCP1 interrupt!
579
        pulsecount = 0;
580
        start_time = now;
581
        while (pulsecount < 256)
582
        {
583
          if (flag_buttonpressed || flag_buttonheld)
584
          {
585
            current_mode = MODE_SPD;
586
            break;
587
          }
588
        }
589
590
        end_time = now;
591
592
        if (pulsecount < 256)
593
        {
594
          current_mode = MODE_SPD;
595
          break;  // a button was pressed before the last pulse was received, abort MODE_CAL.
596
        }
597
598
        sevenseg_bits[2] = sevenseg_bits[1] = sevenseg_bits[0] = 0x00;  // shut off display while writing
599
600
        // our general strategy is counting a fixed number of pulses in a short time, and then
601
        // multiplying that time by a value to obtain calib_factor, and another to get pulses_in_qtrmile
602
        // reminder: 125000 smallticks per second, 4 smallticks per bigtick
603
        // at 50kph, we're going 13.89 m/s
604
        // say we have a wheel circumference of 1.75m (typical kei car small wheel) and 4 pulses per revolution
605
        // therefore it takes us about 8 seconds (1000000 smallticks) to get 256 pulses
606
        // end_time - small_time gives us number of smallticks for 256 pulses, so shift right 8 to divide
607
        // foo is in bigtick*km per pulse*hr
608
        unsigned char to_write = 0xFF;
609
        unsigned long foo = ((makelong(end_time) - makelong(start_time)) * CALIBRATION_SPEED_KPH) >> 8;
610
611
        if (foo)
612
        {
613
          // 3600 sec/hr * 125000 bigticks/sec * 1.609km/mi * 1/4 mi => 181012500 smallticks * km / hr
614
          pulses_in_qtrmile = 181012500 / foo;
615
          // pulses_in_qtrmile = 112500000 / foo; // use this one for mph
616
617
          LOBYTE(to_write, pulses_in_qtrmile);
618
          eeprom_write(eeprom_pulses_in_qtrmile_lo, to_write);
619
          HIBYTE(to_write, pulses_in_qtrmile);
620
          eeprom_write(eeprom_pulses_in_qtrmile_hi, to_write);
621
        }
622
623
        // calib_factor = speed in km/hr * period in bigticks
624
        // divide foo by 4 to convert smallticks to bigticks
625
        calib_factor = (unsigned int)(foo >> 2);
626
        LOBYTE(to_write, calib_factor);
627
        eeprom_write(eeprom_calib_factor_kph_lo, to_write);
628
        HIBYTE(to_write, calib_factor);
629
        eeprom_write(eeprom_calib_factor_kph_hi, to_write);
630
631
        // unfortunately our calib_factor for mph as-is will overflow, so it's commented out
632
        /*
633
        calib_factor = (unsigned int)((foo * 8 / 5) >> 2);
634
        LOBYTE(to_write, calib_factor);
635
        eeprom_write(eeprom_calib_factor_mph_lo, to_write);
636
        HIBYTE(to_write, calib_factor);
637
        eeprom_write(eeprom_calib_factor_mph_hi, to_write);
638
        */
639
640
        // we've burned those values to flash
641
        current_mode = MODE_SPD;
642
        break;
643
      case MODE_DRAG:      // measure quarter mile speed, then display it and time taken
644
        // target number of pulses have been set by MODE_CAL
645
646
        // turn on blinking
647
        gen_timer = tmr1_upper + 10;
648
        blink_enable = 1;
649
        // wait for speed to reach 0, or timeout after 5 seconds or button press.
650
        while (speed != 0 && tmr1_upper != gen_timer)
651
        {
652
          // (meanwhile, calculate and show speed)
653
          calculate_speed(0);
654
          display_number(speed);
655
          if (flag_buttonpressed || flag_buttonheld)
656
          {
657
            break;
658
          }
659
        }
660
        // turn off blinking
661
        blink_enable = 0;
662
663
        // if speed didn't hit zero or if button was pressed before speed got to zero, set MODE_SPD and break.
664
        if (speed != 0)
665
        {
666
          current_mode = MODE_SPD;
667
          break;
668
        }
669
        else
670
        {
671
          // reset pulsecount, watch carefully for the first pulse (up to 60 seconds)
672
          pulsecount = 0;
673
          gen_timer = tmr1_upper + 120;
674
          sevenseg_text(MSG_GO);
675
          while (pulsecount == 0 && tmr1_upper != gen_timer)
676
          {
677
            if (flag_buttonpressed || flag_buttonheld)
678
            {
679
              break;
680
            }
681
          }
682
        }
683
684
        // log time, handily "now" was just updated by CCP1 interrupt!
685
        start_time = now;
686
687
        if (pulsecount == 0)
688
        {
689
          current_mode = MODE_SPD;
690
          break;
691
        }
692
693
        gen_timer = tmr1_upper + 100; // after about 50 seconds we'll give up
694
695
        while ((pulsecount < pulses_in_qtrmile) && (tmr1_upper != gen_timer) && !(flag_buttonpressed || flag_buttonheld))
696
        {
697
          // keep calculating/showing the speed!
698
          calculate_speed(1);  // force updates, ignore hysteresis (we want a nice accurate trap speed)
699
          display_number(speed);
700
        }
701
        // mark stop time
702
        end_time = now;
703
        // if we timed out or chickened out, return to SPD
704
        if (pulsecount < pulses_in_qtrmile)
705
        {
706
          current_mode = MODE_SPD;
707
          break;
708
        }
709
        else
710
        {
711
          // else, we're already showing the trap speed; blink for about 2 seconds
712
          blink_enable = 1;
713
          gen_timer = tmr1_upper + 4;
714
          while (tmr1_upper != gen_timer)
715
          {
716
            ;
717
          }
718
719
          // turn off blink and keep showing trap speed another ~2 seconds
720
          blink_enable = 0;
721
          gen_timer = tmr1_upper + 4;
722
723
          while (tmr1_upper != gen_timer)
724
          {
725
            ;
726
          }
727
728
          // now calculate and show the time for ~4 seconds (and add decimal)
729
          display_number(to_millisecs(makelong(end_time) - makelong(start_time)));
730
          sevenseg_bits[1] |= SEVSEG_DECIMAL_MASK;
731
          gen_timer = tmr1_upper + 8;
732
733
          while (tmr1_upper != gen_timer)
734
          {
735
            ;
736
          }
737
        }
738
        current_mode = MODE_SPD;  // we're done here
739
        break;
740
      case MODE_MENU_DRAG:  // show menu
741
      case MODE_MENU_CAL:    // show menu
742
      case MODE_MENU_TEST:  // show menu
743
        sevenseg_text(current_mode);
744
        gen_timer = tmr1_upper + MENU_TIMEOUT;
745
        while (tmr1_upper != gen_timer)
746
        {
747
          if (flag_buttonpressed)
748
          {
749
            current_mode++;
750
            break;
751
          }
752
          else if (flag_buttonheld)
753
          {
754
            current_mode = current_mode + (MODE_SPD + 1);
755
            break;
756
          }
757
        }
758
#ifdef _DEBUG
759
        if (current_mode == MODE_MENU_TEST + 1)
760
#else
761
        if (current_mode == MODE_MENU_CAL + 1)
762
#endif
763
          current_mode = MODE_MENU_DRAG;    // let's wrap around.
764
        if (tmr1_upper == gen_timer)
765
          current_mode = MODE_SPD;
766
        break;
767
#ifdef _DEBUG
768
      case MODE_TEST:      // display test mode
769
        // TEST: running numbers. display tmr1_upper on the LED until button is pressed
770
        while (!(flag_buttonpressed || flag_buttonheld))
771
        {
772
          display_number(tmr1_upper);
773
          if (tmr1h.7)  // blink all dots on and off to show we are in test mode
774
          {
775
            sevenseg_bits[2] |= SEVSEG_DECIMAL_MASK;
776
          }
777
          sevenseg_bits[0] |= (BUTTON_INPUT ? SEVSEG_DECIMAL_MASK : 0);
778
          sevenseg_bits[1] |= (SPEEDO_INPUT ? SEVSEG_DECIMAL_MASK : 0);
779
        }
780
        current_mode = MODE_TEST2;
781
        break;
782
      case MODE_TEST2:      // button input test mode for 15 seconds, button doesn't change modes
783
        // TEST2: Check our button
784
        gen_timer = tmr1_upper + 30;
785
        display_number(0);
786
        while (gen_timer != tmr1_upper)
787
        {
788
          if (button_heldcount > 0)
789
            display_number(button_heldcount);
790
          if (tmr1h.7)  // blink all dots on and off to show we are in test mode
791
          {
792
            sevenseg_bits[1] |= SEVSEG_DECIMAL_MASK;
793
            sevenseg_bits[0] |= SEVSEG_DECIMAL_MASK;
794
          }
795
          sevenseg_bits[2] |= (BUTTON_INPUT ? SEVSEG_DECIMAL_MASK : 0);
796
        }
797
        current_mode = MODE_TEST3;
798
        break;
799
      case MODE_TEST3:      // speedo input test mode
800
        // TEST3: count pulses to test speedometer input
801
        pulsecount = 0;
802
        while (!(flag_buttonpressed || flag_buttonheld))
803
        {
804
          if (pulsecount > 512)
805
            pulsecount = 0;    // wrap because we don't display numbers higher than 512
806
          display_number(pulsecount);
807
          if (tmr1h.7)  // blink all dots on and off to show we are in test mode
808
          {
809
            sevenseg_bits[2] |= SEVSEG_DECIMAL_MASK;
810
            sevenseg_bits[1] |= SEVSEG_DECIMAL_MASK;
811
            sevenseg_bits[0] |= SEVSEG_DECIMAL_MASK;
812
          }
813
        }
814
        current_mode = MODE_SPD;
815
        break;
816
#endif // #ifdef _DEBUG
817
      default:
818
        // We should never arrive here.
819
        // if we're debugging, we want to know about this; print MSG_EEE and lockup.
820
        sevenseg_text(MSG_EEE);
821
        while (!flag_buttonpressed)
822
          ;
823
        // if we're not debugging, just get us back into MODE_SPD.
824
        current_mode = MODE_SPD;
825
    }
826
827
    clear_wdt();
828
  }
829
}
830
831
// handles ALL interrupts
832
void interrupt(void)
833
{
834
  // Determine which interrupt(s) we've gotten, and act accordingly.
835
836
  // tmr2 overflow, refresh next LED cluster
837
  if (tmr2if)
838
  {
839
    refresh_sevenseg();
840
    tmr2if = 0;
841
  }
842
843
  // tmr1 overflow, increment tmr1 "most significant bits" global var (since we can't set the prescaler high enough)
844
  // THIS MUST COME BEFORE CCP HANDLER
845
  if (tmr1if)
846
  {
847
    tmr1_upper++;
848
    // expire stale pulses
849
    if (tmr1_upper - lastpulses[1].high8 > PULSE_LIFETIME)
850
      lastpulse_stale1 = 1;
851
    if (tmr1_upper - lastpulses[0].high8 > PULSE_LIFETIME / 2)
852
      lastpulse_stale0 = 1;
853
854
    tmr1if = 0;
855
  }
856
857
  // tmr1 could roll over here! tmr1_upper would be inconsistent.
858
859
  // CCP trigger, happens when we get a speedometer pulse.
860
  if (ccp1if)
861
  {
862
    now.high8 = tmr1_upper;
863
864
    // Normally, stopping captures would be bad, but since we just triggered,
865
    // any captures that happen now are bounces.
866
    ccp1con = 0b00000000;    // stop it from capturing while we're reading the value
867
    now.mid8 = ccpr1h;
868
    now.low8 = ccpr1l;
869
    ccp1con = 0b00000100;
870
871
    // If tmr1if is uncleared, it means tmr1_upper we got is inconsistent.
872
    // Don't clear the ccp1if flag, we'll catch it on the next pass.
873
    if(!tmr1if)
874
    {
875
      // if it's too close to the last pulse, ignore it, it's just a bouncy switch
876
877
      if ((make16i(now) - make16i(lastpulses[0])) > SPEEDO_BOUNCE_THRESHOLD) // 2ms => 250 ticks => 62.5 bigticks
878
      {
879
        pulsecount++;
880
        lastpulses[1] = lastpulses[0];
881
        lastpulse_stale1 = lastpulse_stale0;
882
        lastpulses[0] = now;
883
        lastpulse_stale0 = 0;
884
      }
885
      ccp1if = 0;
886
    }  // else: don't worry about "now", as soon as we return from interrupt we'll hop back in again.
887
  }
888
889
  if (t0if)  // button polling routine (every 32.768 ms)
890
  {
891
    // the flags will be cleared externally
892
    if (!BUTTON_INPUT)  // if button is pressed, BUTTON_INPUT is held low.
893
    {
894
      button_heldcount++;
895
      // if the button has been on 30 times in a row (about 1 second), flag it as being held
896
      if (button_heldcount == 30)
897
        flag_buttonheld = 1;
898
      // if the button has been on more than 30 times in a row, we've already flagged it as held
899
      // if the button has been on THIS long, the user is obviously on drugs
900
      else if (button_heldcount == 0xFF)
901
        button_heldcount--;  // so as not to overflow
902
    }
903
    else
904
    {
905
      // if the button hasn't been pressed, nothing to report.
906
      // if the button was released after being on once or twice, it was a glitch.
907
      // if the button was released after being on between 2 and 29 times in a row, it was pressed
908
      if ((button_heldcount > 1) && (button_heldcount < 30))
909
        flag_buttonpressed = 1;
910
      // if the button was released after being on more than that, we already flagged it as held.
911
      button_heldcount = 0;
912
    }
913
    t0if = 0;
914
  }
915
}
916
917
unsigned char eeprom_read(unsigned char address)
918
{
919
  eeadr = address;
920
  eecon1.RD = 1;
921
  return eedata;
922
}
923
924
// Write a byte of data to the given EEPROM address.
925
// This function will block execution if a previous write hasn't finished yet.
926
927
void eeprom_write(unsigned char address, unsigned char data)
928
{
929
  while (eecon1.WR)
930
    ;
931
  eeadr = address;
932
  eedata = data;
933
  gie = 0;
934
  eecon1.WREN = 1;  // enable writes
935
  // write sequence
936
  eecon2 = 0x55;
937
  eecon2 = 0xAA;
938
  eecon1.WR = 1;
939
940
  eecon1.WREN = 0;  // disable writes
941
  gie = 1;
942
943
}