Automotive speedometer for a camper
Converts mechanical speedo- and tachometer signal to digital. Display speed, rpm, trip, odometer on a LCD + "analog" speed using a servo.
Components and supplies
1
SG90 Micro-servo motor
1
Arduino UNO
1
Photo resistor
1
Alphanumeric LCD, 16 x 2
1
Analog to digital speedometer converter
Project description
Code
speedometer_only_dieseldoris
arduino
Converts a mechanical speedometer signal to digital and displays speedometer and odometer data on a LCD display.
1/* 2 "Speedometer for Diesel-Doris" 3 4 Converts an aftermarket mechanical speedometer signal to digital. 5 The speedometer converter output is a frequency signal (1 pulse cycle/revolution). 6 The mechanical speedometer output is ~1,307 m/revolution. 7 8 Emil Backram 9 20 Dec 2020 - Project start (Uno Rev.3) 10 26 Dec 2020 - Succsessful lab test of basic algorithm - serial printer 11 27 Dec 2020 - Succsessful lab test of basic algorithm - LCD 12 29 Dec 2020 - Semi-succsessful lab test of variable contrast and back-light: 13 * Fixed setpoint back-light levels - OK 14 * Variable back-light level - unstable 15 * All contrast levels inbetween ON and OFF - flickery (Note: No RC-filter!) 16 07 Jan 2021 - Succsessful lab test of 17 * new basic algorithm: 18 + Added timer interrupt 19 + Count number of pulses every 250ms 20 + Calc speed using the sum of the last 4 puls counts 21 * variable contrast and back-light: 22 + Smoth back-light level transition 23 + Fixed the contrast flickering bug (digitalWrite --> analogWrite) 24 + Added RC-filter to contrast output (HW) 25 * new trip and odometer meter algoritm: 26 + Store odometer data in eeprom 27 + Calculate trip data using the diff of odometer data at startup and current odometer data 28 08 Jan 2021 - Rearranged #define constants in 2 groups, 'configurable' and 'non-configurable' 29 12 Jan 2021 - Fixed EEPROM read bug when > 2 bytes - cast back to 'unsigned long' 30 13 Jan 2021 - Updated comments and clarifications 31 - Splited read/write from/to memory subroutine into separate read respective write subroutines 32 15 Jan 2021 - Changed display delay timer from calculating "next time" using millis() to using a bool variable set in timer1 interrupt 33 17 Jan 2021 - Minor updates: 34 * Notes 35 * Names 36 * LCD visual appearance 37 * Removed unused defines 38 * Made eeprom read/write odometer data subroutines more generic --> unsigned long read/write subroutines 39 * Moved some processing from timer1 interrupt to main loop 40 22 Jan 2021 - Minor updates: 41 * Activate on chip pullup for SPEED_PIN to match the hardware design 42 * Use pin 9 instead of 11 for the LCD, in order to free the last usable PWM capable pin 43 + Pin 3 and 11 use timer2 for PWM control 44 + Pin 9 and 10 won't be used for PWM since timer1 is used for timer interrupt 45 25 Jan 2021 - Added servo control for "analog" speedometer 46 26 Jan 2021 - Added RPM handling 47 * Display RPM on the LCD 48 * "Shiftlight", LCD light blinks two times when RPM is too high or low 49 27 Jan 2021 - Minor optimizations to minimize execution time and program memory space 50 - Fixed minor bug for "Shiftlight" 51 02 Feb 2021 - Updated shiftlight enable functionality (only enabled when vehicle speed is within a certain interval) 52 18 Apr 2021 - Updated speed pulse sensor data. Test result: Mechanical speedometer wire output is 0,765 m/revolution 53 20 Apr 2021 - Removed engine speed, and its related functionalities (for now, due to mechanical difficulties) 54 - Fixed brain somersault: 0,765 = revolutions/m --> ~1,307 m/revolution 55 - Changed interrupt trigger from RISING (1 pulse/revolution) to CHANGE (2 pulses/revolution) to increase resolution 56 * ~1,307 m/revolution --> ~0,654 m/pulse 57 03 May 2021 - Fixed light intensity bug (missing analog write) 58 - Added floating average for vehicle speed 59 18 May 2021 - Fixed floating average for vehicle speed bug 60*/ 61 62/* Libraries */ 63#include <EEPROM.h> 64#include <LiquidCrystal.h> 65 66/* Pin definitions */ 67LiquidCrystal lcd(4, 7, 8, 9, 12, 13); //Pins chosen to avoid IRQ inputs and PWM outputs (Uno) 68#define SPEED_PIN 2 //Dig pin 2 = IRQ 0 (Uno) 69#define LCD_CONTRAST_PIN 5 //PWM pin 5 using Timer0 (Uno) 70#define LCD_LIGHT_PIN 6 //PWM pin 6 using Timer0 (Uno) 71#define SERVO_PIN 11 //PWM pin 11 using Timer2 (Uno) 72#define LIGHT_SENS_PIN A0 //Analog pin 0 73 74/* Non-configurable system-specific constants */ 75#define UPDATE_FREQUENCY 4 //Hz 76#define OCR1A_VAL 62499 //Corresponds to UPDATE_FREQUENCY 77#define BYTE_SHIFT 8 //(bits) 78#define ODO_ADDR 0 //Odometer data address in eeprom 79#define LCD_ROWS 2 //Number of rows in the LCD display 80#define LCD_COLUMNS 16 //Number of columns in the LCD display 81#define LCD_UPPER 0 //(row) 82#define LCD_LOWER 1 //(row) 83#define LCD_INIT_COLUMN 0 //(column) 84#define LCD_HALF_COLUMN 8 //(column) 85#define OCR2A_MIN 7 //Corresponds to 180 degrees @ ~61Hz (Attention!!! This works for my servo, it might damage yours! I found the limits through carefull trial and error!) 86#define OCR2A_MAX 34 //Corresponds to 0 degrees @ ~61Hz (Attention!!! This works for my servo, it might damage yours! I found the limits through carefull trial and error!) 87 88/* Configurable vehicle-specific constants */ 89#define M_PER_PULSE 0.654f //(m/pulse) Using CHANGE instead of RISING for the interrupt will double the pulses per revolution 90#define SPEED_MIN 5 //(km/h) 91#define KPH_PER_SERVO_STEP 5 //(km/h)/step 92#define LIGHT_SENS_LIMIT 300 //0-1023 93#define LCD_LIGHT_MAX 255 //LCD_LIGHT_MIN-255 94#define LCD_LIGHT_MIN 10 //0-LCD_LIGHT_MAX 95#define LCD_CONTRAST 25 //0-255 96#define STARTUP_SCREEN_DELAY 2500 //(ms) 97 98/* Macros */ 99#define MIN_CONSTRAINT(X, MIN) ((X > MIN) ? X : MIN) 100#define MAX_CONSTRAINT(X, MAX) ((X < MAX) ? X : MAX) 101#define VAL_CONSTRAINT(X, MIN, MAX) MAX_CONSTRAINT(MIN_CONSTRAINT(X, MIN), MAX) 102 103#define DISTANCE_CALC(X) ((X * M_PER_PULSE) / 1000.f) //Input (float)pulses, returns (km) 104#define SPEED_CALC(X) (X * M_PER_PULSE * 3.6f) //Input (float)pulses/s, returns (km/h) 105 106#define KPH_TO_SPEEDO_SERVO(KPH) VAL_CONSTRAINT((OCR2A_MAX - (KPH / KPH_PER_SERVO_STEP)), OCR2A_MIN, OCR2A_MAX) //Input km/h, returns PWM setpoint (OCR2A_MIN - OCR2A_MAX) 107 108/* Global variables */ 109bool volatile timer1_interrupt = false; 110byte volatile orc2a_val = OCR2A_MIN; 111byte volatile irq_array_cntr = 0; 112unsigned int volatile speed_irq_cntr = 0; 113unsigned int volatile speed_pulses_array[UPDATE_FREQUENCY] = {0}; 114 115/* Subroutines */ 116void display_vehicle_data(byte, unsigned long, unsigned long); 117unsigned long eeprom_read_u_long(unsigned int); 118void eeprom_write_u_long(unsigned int, unsigned long); 119 120/* Setup */ 121void setup() 122{ 123 /* Pins */ 124 pinMode(SPEED_PIN, INPUT_PULLUP); //Activate pullup resistor for speed interrupt pin 125 pinMode(LIGHT_SENS_PIN, INPUT); 126 pinMode(LCD_LIGHT_PIN, OUTPUT); 127 pinMode(LCD_CONTRAST_PIN, OUTPUT); 128 pinMode(SERVO_PIN, OUTPUT); //Output correspond to orc2a_val (no analogWrite() in code) 129 130 /* Interrupts */ 131 attachInterrupt(digitalPinToInterrupt(SPEED_PIN), speed_isr, CHANGE); //Using CHANGE instead of RISING for the interrupt will double the pulses per revolution 132 133 cli(); //Clear global Interrupts 134 135 /* Timer1 */ 136 TCCR1A = 0; //Clear register, CTC mode 137 TCCR1B = 0; //Clear register 138 TCNT1 = 0; //Clear register 139 OCR1A = OCR1A_VAL; //Corresponds to UPDATE_FREQUENCY 140 TCCR1B |= (1 << WGM12); //CTC mode 141 TCCR1B |= (1 << CS11) | (1 << CS10); //64 prescaler, corresponds to UPDATE_FREQUENCY 142 TIMSK1 |= (1 << OCIE1A); //Output Compare A Match Interrupt Enable 143 144 /* Timer2 (analog speed) */ 145 TCCR2A = 0; //Clear register 146 TCCR2B = 0; //Clear register 147 TCNT2 = 0; //Clear register 148 OCR2A = OCR2A_MIN; //Initial setpoint at startup (OCR2A_MIN --> 'max' km/h) 149 TCCR2A |= (1 << COM2A1); //Compare Output Mode (non-inverting mode) 150 TCCR2A |= (1 << WGM21) | (1 << WGM20); //Fast PWM Mode 151 TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20); //1024 prescaler 152 TIMSK2 |= (1 << TOIE2); //Overflow Interrupt Enable 153 154 sei(); //Set global interrupts 155 156 /* LCD */ 157 analogWrite(LCD_CONTRAST_PIN, LCD_CONTRAST); 158 lcd.begin(LCD_COLUMNS, LCD_ROWS); 159 lcd.setCursor(LCD_INIT_COLUMN, LCD_UPPER); 160 lcd.print("<<<< Diesel >>>>"); 161 lcd.setCursor(LCD_INIT_COLUMN, LCD_LOWER); 162 lcd.print("<<<< Doris >>>>"); 163 analogWrite(LCD_LIGHT_PIN, LCD_LIGHT_MAX); 164 delay(STARTUP_SCREEN_DELAY); 165 lcd.clear(); 166 167 /* Analog speed (OCR2A_MAX --> 0 km/h) */ 168 orc2a_val = OCR2A_MAX; 169} 170 171/* Main */ 172void loop() 173{ 174 static bool run_once = false; 175 static unsigned long accumulated_speed_pulses = 0; 176 static const unsigned long init_odometer = eeprom_read_u_long(ODO_ADDR); //Fetch latest odometer data at startup 177 178 /* Calculate total distance (km) */ 179 unsigned long current_odometer = (unsigned long)DISTANCE_CALC((float)accumulated_speed_pulses) + eeprom_read_u_long(ODO_ADDR); 180 181 /* Calculate speed (km/h) and store odometer data */ 182 unsigned long speed_pulses_per_s = 0; 183 for(byte array_cntr = 0; array_cntr < UPDATE_FREQUENCY; array_cntr++) 184 { 185 speed_pulses_per_s += speed_pulses_array[array_cntr]; 186 } 187 188 float speed_kph = SPEED_CALC((float)speed_pulses_per_s); 189 if(speed_kph < SPEED_MIN) 190 { 191 speed_kph = 0.f; 192 accumulated_speed_pulses = 0; 193 if(run_once) //Store odometer data in eeprom (only once when vehicle no longer is in motion) 194 { 195 eeprom_write_u_long(ODO_ADDR, current_odometer); 196 run_once = false; 197 } 198 } 199 else 200 { 201 run_once = true; 202 } 203 204 /* Run when enabled by timer1 interrupt (Corresponds to UPDATE_FREQUENCY) */ 205 if(timer1_interrupt) 206 { 207 static float vehicle_speed_array[UPDATE_FREQUENCY] = {0.f}; 208 timer1_interrupt = false; 209 accumulated_speed_pulses += speed_pulses_array[irq_array_cntr]; //Accumulate latest odometer data 210 211 irq_array_cntr++; 212 if(irq_array_cntr >= UPDATE_FREQUENCY) 213 { 214 irq_array_cntr = 0; 215 } 216 217 /* Vehicle speed average */ 218 vehicle_speed_array[irq_array_cntr] = speed_kph; 219 float avg_speed_kph = 0; 220 for(byte array_cntr = 0; array_cntr < UPDATE_FREQUENCY; array_cntr++) 221 { 222 avg_speed_kph += vehicle_speed_array[array_cntr]; 223 } 224 avg_speed_kph = avg_speed_kph/(float)UPDATE_FREQUENCY; 225 226 /* Display vehicle data (and calculate distance since startup (km)) */ 227 display_vehicle_data((byte)avg_speed_kph, (current_odometer - init_odometer), current_odometer); 228 } 229} 230 231/* Display vehicle data */ 232void display_vehicle_data(byte vehicle_speed, unsigned long vehicle_trip, unsigned long vehicle_odometer) 233{ 234 static byte lcd_light_intensity = 0xff; 235 236 /* LCD light intensity setpoint */ 237 byte lcd_light_intensity_setpoint; 238 if(analogRead(LIGHT_SENS_PIN) > LIGHT_SENS_LIMIT) 239 { 240 lcd_light_intensity_setpoint = LCD_LIGHT_MAX; 241 } 242 else 243 { 244 lcd_light_intensity_setpoint = LCD_LIGHT_MIN; 245 } 246 247 if(lcd_light_intensity < lcd_light_intensity_setpoint) 248 { 249 lcd_light_intensity++; 250 } 251 else if(lcd_light_intensity > lcd_light_intensity_setpoint) 252 { 253 lcd_light_intensity--; 254 } 255 analogWrite(LCD_LIGHT_PIN, lcd_light_intensity); 256 257 /* Analog speed */ 258 orc2a_val = KPH_TO_SPEEDO_SERVO(vehicle_speed); 259 260 /* LCD vehicle data */ 261 //No lcd.clear() to avoid display flickering. Lingering artifacts are avoided anyway in the current design 262 lcd.setCursor(LCD_INIT_COLUMN, LCD_UPPER); 263 lcd.print(vehicle_speed); 264 lcd.print("km/h "); //'Blank space' at the end to clear lingering artifacts when speed is decreasing 265 lcd.setCursor(LCD_INIT_COLUMN, LCD_LOWER); 266 lcd.print(vehicle_trip); 267 lcd.print("km"); 268 lcd.setCursor(LCD_HALF_COLUMN, LCD_LOWER); 269 lcd.print(vehicle_odometer); 270 lcd.print("km"); 271} 272 273/* Read unsigned long data from eeprom */ 274unsigned long eeprom_read_u_long(unsigned int addr) 275{ 276 unsigned long read_value = 0; 277 for(byte byte_cntr = 0; byte_cntr < sizeof(read_value); byte_cntr++) 278 { 279 read_value |= (unsigned long)EEPROM.read(addr + byte_cntr) << (BYTE_SHIFT * byte_cntr); 280 } 281 return read_value; 282} 283 284/* Write unsigned long data to eeprom */ 285void eeprom_write_u_long(unsigned int addr, unsigned long write_value) 286{ 287 for(byte byte_cntr = 0; byte_cntr < sizeof(write_value); byte_cntr++) 288 { 289 EEPROM.update((addr + byte_cntr), (byte)(write_value >> (BYTE_SHIFT * byte_cntr))); 290 } 291} 292 293/* Speed sensor ISR */ 294void speed_isr(void) 295{ 296 speed_irq_cntr++; 297} 298 299/* Timer1 ISR */ 300ISR(TIMER1_COMPA_vect) 301{ 302 speed_pulses_array[irq_array_cntr] = speed_irq_cntr; 303 speed_irq_cntr = 0; 304 timer1_interrupt = true; 305} 306 307/* Timer2 ISR */ 308ISR(TIMER2_OVF_vect) 309{ 310 OCR2A = orc2a_val; 311} 312
Comments
Only logged in users can leave comments