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 | }
|