Vehicle logger design stage

HI guys,

I'm fairly new to this programming thing, although I have been through a lot of the bundled tutorials and samples that came with the IDE software, so I have some idea what I am doing.

What I am after from you guys is a little guidance on the methods to achieve what I want to do with this logger, so I will lay out what I am wanting to do and what I have available to me.
I am open to suggestions on doing things differently if its a better way.

What I would like to do is have a portable logger that I can plug in to a car, with minimal work, to any car, so standalone sensors and no OBDII integration at all. The main car I want to use this for is old enough that the only electronics on board is the ignition module, and of course my smart phone!

I am aiming to log RPM for the engine and also wheel speed via drive shaft, using IR break beam/reflectors on rotating parts.
I would also like to log acceleration from an accelerometer breakout, as well as some analog inputs, throttle position sensor (potentiometer), and a 0-5v signal from a stand alone O2 sensor reader.
All of this will be sampling and saving to SD card.
I wish to have this start and stop on pushbutton. but in reality just a stop button will be fine, and reset to restart logging will work.
I am looking to sample all these values at least 10 times a second, I did want to use GPS for speed, but reading and parsing the data takes too long for these update rates.

I have done some maths on numbers of reflectors to use to get accurate pulse signal data from low speed up, and depending on what time is required for sampling I can change the number of reflectors to scale up or down to a suitable rate.

equipment available to me:
Arduino UNO and Funduino Mega
adafruit GPS module
ADXL345 digital accelerometer
ADXL345 analog accelerometer
2x break beam sensors
2x frequency to voltage converters (if need to go analog)
SD card breakout
SD logging shield with RTC
2x16 LCD
prototyping boards and cables and misc components.

code plan:
I wish to sample at 10Hz, so thats 100ms cycling. How long does SD write cycle take?
I'm thinking that I need to count RPM pulses for ~50ms each, one after the other, then sample analog ports and write to file, then repeat.
I have so far been unable to work out logging accel data from digital sensor to SD card, I can make it stream to serial window but that's as far as I could get.
I'm a bit time poor with work and new baby.

At worst I am working on using all analog inputs, accel inputs, freq to volt converters and sample and write if need be.

If you have any suggestions on better ways to do this, please let me know, but this is basically what I am thinking to get started.

do you think for RPM count, using Pulsein command with a 50ms timeout might be a better way of getting a result?
Or would using a counter incremented every detection of a state change of digital input, for a fixed period of 50 ms, using a millis() calculation, number noted and recorded, and then reset every time it is called be better?
for pulse detection, is digitalread fast enough? or is PulseIn better to use?
Fast enough being up to 1Khz if I scale it that high, or 500Hz.

IR beam break sensing is likely not going to be reliable enough in a car. The heat from the engine alone will likely drown out the sensor.

For older pre-computer cars the usual method for obtaining the rpm signal is from the coil ground or if available a tach output on the ignition module. Signal conditioning will be necessary to avoid damaging the arduino. Typically an interrupt and a counter is used to read the signal.

Here is one of the better write ups that I seen: Nixietach II

For the Speedometer/wheelspeed a magnet mounted to the driveshaft read by a hall effect sensor or inductive proximity sensor would be more reliable.

No time for more, gots to go to work. But this should get you started

you may be right about using hall effect instead of IR, but they are what I have so I will try them out first.
I should mention that the aim of this is for short term testing, not to be left connected for long term.
for this reason, and being temporary connected, I do not wish to pull apart the dash to get a tach signal every time I hook it up to a different car.
The main car I have in mind to log and measure tune state on, has an MSD ignition box, which represents problems when getting readings off the coil wiring.
The first problem, is it is charging the coil off 400V, but the main issue is the multiple spark ignition.
This fires 3 times per ignition triggering below 1500, and then drops to 2 then 1 by 2500rpm, making it more difficult to measure low speeds.
This is why I was thinking about using optical off the crank pulley, and I can add as many reflectors as needed to get a good resolution at low speeds and fast sampling.
Hall effect may be better, and could be easily substituted.

The 12v ground wire for the coil would be on the engine so no need to go under the dash and you certainly would not want to use the high voltage output.

But since you have an MSD box, it has a 12v tach output on it that you can use. You will need a voltage divider to reduced the voltage to 5v. And input protection to protect the arduino.

Here are a few good links on that subject:

http://www.thebox.myzen.co.uk/Tutorial/Protection.html

powergravity developed a Scooter logger that had GPS @ 20Hz and several other sensors @ 100Hz, all logged to an SD card. Lengthy thread starts here, last version of software here. Related threads of interest can be found by looking at powergravity's posts.

One of the main problems is that the SD card will occasionally take more than 100ms to complete a write operation. And after writing about 1MB of data, it would take even longer to write one 512-byte buffer, almost 1s. o_O

To avoid losing sensor & GPS data during those operations, several advanced techniques were used:

  • GPS data is parsed during the RX character interrupt, using NeoGPS & NeoHWSerial. NeoGPS saves "fix" structures in a queue until they can be handled (i.e., written to the SD). The fix structures are much smaller than the raw character data, so more GPS updates could be saved in the limited RAM.

  • The IMU he used had several "quirks" that we discovered. The BNO055 does not quite operate at 100Hz, so it required a more flexible file format. It would probably not work on any of the SAM MCUs (e.g., Teensy).

  • The SdFat library was modified to call his function to read samples while the SD write was completing.

Cheers,
/dev

ok so if anyone is interested,
I have ditched the requirement for GPS, because simply, if I log drive shaft RPM, 1 calc and I have speed, easy.
I will be using hall effect for RPM pickup.
So I learnt a bit more about how to use interrupts, and decided that they will do the job.
So anyway here is my code.

Datalogger with 2 RPM inputs, 2 analog inputs and reading ADXL345 accelerometer sensor, sampling at 50ms saving to SD every 500ms.

Tested on the bench with 2 other arduinos producing pulse stream frequency sweep to simulate varying RPMs at 2 different rates, from 31Hz (lowest tone arduino can produce) to 500Hz (my calculated max RPM with 4 magnets)
feel free to comment or suggest improvements.

#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include "RTClib.h"
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);


#define LOG_INTERVAL  50 // mills between entries (reduce to take more/faster data)
#define SYNC_INTERVAL 500 // mills between calls to flush() - to write data to the card
uint32_t syncTime = 0; // time of last sync()

#define redLEDpin 4
#define greenLEDpin 5

#define TPS_pin 0           // analog 0
#define lambda_pin 1                // analog 1

RTC_PCF8523 RTC;

const int chipSelect = 10;

volatile float time1 = 0;
volatile float time_last1 = 0;
volatile float time2 = 0;
volatile float time_last2 = 0;
int rpm1_k = 4; //pulse divider - no of magnets on shaft
int rpm2_k = 4;


// the logging file
File logfile;

void error(char *str)
{
 Serial.print("error: ");
 Serial.println(str);

 // red LED indicates error
 digitalWrite(redLEDpin, HIGH);

 while (1);
}

void setup(void)
{
 Serial.begin(9600);
 Serial.println();

 pinMode(redLEDpin, OUTPUT);
 pinMode(greenLEDpin, OUTPUT);

 Serial.print("Initializing SD card...");
 pinMode(10, OUTPUT);
 if (!SD.begin(chipSelect)) {
   error("Card failed, or not present");
 }
 Serial.println("card initialized.");
 char filename[] = "LOGGER00.CSV";
 for (uint8_t i = 0; i < 100; i++) {
   filename[6] = i / 10 + '0';
   filename[7] = i % 10 + '0';
   if (! SD.exists(filename)) {
     // only open a new file if it doesn't exist
     logfile = SD.open(filename, FILE_WRITE);
     break;
   }
 }

 if (! logfile) {
   error("couldnt create file");
 }

 Serial.print("Logging to: ");
 Serial.println(filename);

 !accel.begin();
 accel.setRange(ADXL345_RANGE_2_G);

 // connect to RTC
 Wire.begin();
 if (!RTC.begin()) {
   logfile.println("RTC failed");
 }
 logfile.println("millis,datetime,TPS,Lambda,RPM1,RPM2,X,Y,Z");
 {
   attachInterrupt(0, rpm1, RISING);
   attachInterrupt(1, rpm2, RISING);
 }
}

void loop(void)

{
 int rpm1 = 0;
 if (time1 > 0)
 {
   rpm1 = 60 * ((1000000 / (time1)) / rpm1_k);
 }
 int rpm2 = 0;
 if (time2 > 0)
 {
   rpm2 = 60 * ((1000000 / (time2)) / rpm2_k);
 }

 DateTime now;
 delay((LOG_INTERVAL - 1) - (millis() % LOG_INTERVAL));

 digitalWrite(greenLEDpin, HIGH);

 uint32_t m = millis();
 logfile.print(m);
 logfile.print(", ");

 now = RTC.now();
 logfile.print(now.year(), DEC);
 logfile.print("/");
 logfile.print(now.month(), DEC);
 logfile.print("/");
 logfile.print(now.day(), DEC);
 logfile.print(" ");
 logfile.print(now.hour(), DEC);
 logfile.print(":");
 logfile.print(now.minute(), DEC);
 logfile.print(":");
 logfile.print(now.second(), DEC);
 logfile.print('"');

 analogRead(TPS_pin);
 delay(10);
 int TPS = analogRead(TPS_pin);

 analogRead(lambda_pin);
 delay(10);
 int lambda = analogRead(lambda_pin);


 logfile.print(", ");
 logfile.print(TPS);
 logfile.print(", ");
 logfile.print(lambda);
 logfile.print(", ");
 logfile.print(rpm1);
 logfile.print(", ");
 logfile.print(rpm2);
 logfile.print(", ");


 sensors_event_t event;
 accel.getEvent(&event);

 logfile.print(event.acceleration.x);
 logfile.print(", ");
 logfile.print(event.acceleration.y);
 logfile.print(", ");
 logfile.print(event.acceleration.z);

 logfile.println();


 digitalWrite(greenLEDpin, LOW);

 if ((millis() - syncTime) < SYNC_INTERVAL) return;
 syncTime = millis();

 // blink LED to show we are syncing data to the card & updating FAT!
 digitalWrite(redLEDpin, HIGH);
 logfile.flush();
 digitalWrite(redLEDpin, LOW);

}

void rpm1()
{
 time1 = (micros() - time_last1);
 time_last1 = micros();
}
void rpm2()
{
 time2 = (micros() - time_last2);
 time_last2 = micros();
}

Hi,
What Arduino have you written the above code for?
Have you successfully compiled it?

Tom.... :slight_smile:

Hi, I started working on an UNO and then bought a MEGA a couple of weeks back and compiled it on that initially. Today I put it on the UNO to see if that worked and it seems fine even at 20Hz sampling.
I forgot to mention that I'm using an Adafruit data logging shield for the SD card and RTC functions.

I was initially using the UNO and a NANO to generate test signals. I can post that code too if anyone is interested.

hey all!

I’m so close now with my code, I added some functions, such as live readout display of values on LCD, and a function to display only on LCD with push button to start logging.
I had a break from it all, and got back into it the other day, and having some problems compiling.
I’m sure its really simple and I’ve just missed something basic, but for the life of me I can’t spot it!
If anyone can help, it would really be appreciated.
thanks in advance.

#include <RTClib.h>
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345);
#include <Adafruit_LiquidCrystal.h>
Adafruit_LiquidCrystal lcd(0);

#define LOG_INTERVAL  50 // mills between entries (reduce to take more/faster data)
#define SYNC_INTERVAL 500 // mills between calls to flush() - to write data to the card
uint32_t syncTime = 0; // time of last sync()

#define redLEDpin 4        //digital 4
#define greenLEDpin 5      //digital 5
#define TPS_pin 0          // analog 0
#define lambda_pin 1       // analog 1

RTC_PCF8523 RTC;

const int chipSelect = 10; //digitl pin 10

volatile float time1 = 0;
volatile float time_last1 = 0;
volatile float time2 = 0;
volatile float time_last2 = 0;
int rpm1_k = 4; //pulse divider - no of magnets on shaft
int rpm2_k = 4;

const int TPS_Min = 100;  // TPS values min and max
const int TPS_Max = 864;

const int  StartButton = 2;
int Starter = 0;
int buttonState = 0;             
int lastButtonState = HIGH;   
unsigned long lastDebounceTime = 0;  
unsigned long debounceDelay = 50;    

int X1 = 0;
int Y1 = 0;
int Z1 = 0;
int X2 = 0;
int Y2 = 0;
int Z2 = 0;

volatile int rpm1 = 0;
volatile int rpm2 = 0;

int TPS = 0;
int lambda = 0;
int AFR = 0;

// the logging file
File logfile;

void error(char *str)
{
  Serial.print("error: ");
  Serial.println(str);

  // red LED indicates error
  digitalWrite(redLEDpin, HIGH);

  while (1);
}

void setup()
{
  Serial.begin(9600);
  Serial.println();
  
  lcd.begin(20, 3);
  lcd.setBacklight(LOW);
  
  !accel.begin();
  accel.setRange(ADXL345_RANGE_2_G);
  
  sensors_event_t event;
  accel.getEvent(&event);
  
  X1 = (event.acceleration.x);
  Y1 = (event.acceleration.y);
  Z1 = (event.acceleration.z);
  

  pinMode(redLEDpin, OUTPUT);
  pinMode(greenLEDpin, OUTPUT);
  
  pinMode(StartButton, INPUT_PULLUP);

  Serial.print("Initializing SD card...");
    
    lcd.setCursor(0, 3);
    lcd.print("Initializing SD card");
    
  pinMode(10, OUTPUT);
  if (!SD.begin(chipSelect)) {
    error("Card failed, or not present");
    lcd.setCursor(0, 3);
    lcd.print("Card failed.        ");
    
  }
  Serial.println("card initialized.");
  lcd.setCursor(0, 3);
    lcd.print("card initialized.      ");
  
  char filename[] = "LOGGER00.CSV";
  for (uint8_t i = 0; i < 100; i++) {
    filename[6] = i / 10 + '0';
    filename[7] = i % 10 + '0';
    if (! SD.exists(filename)) {
      // only open a new file if it doesn't exist
      logfile = SD.open(filename, FILE_WRITE);
      break;
    }
  }

  if (! logfile) {
    error("couldnt create file");
  }

  Serial.print("Logging to: ");
  Serial.println(filename);
    lcd.setCursor(0, 3);
    lcd.print("Log To:");
    lcd.setCursor(7, 3);
    lcd.print(filename);


  // !accel.begin();
  // accel.setRange(ADXL345_RANGE_2_G);

  // connect to RTC
  Wire.begin();
  if (!RTC.begin()) {
    logfile.println("RTC failed");
    lcd.setCursor(0, 3);
    lcd.print("RTC failed          ");
  }
  logfile.println("millis,datetime,TPS,Lambda,AFR,RPM1,RPM2,X,Y,Z");
  {
    attachInterrupt(0, rpm1, RISING); // digital pin 0
    attachInterrupt(1, rpm2, RISING); //digital pin 1
  }
}

void loop()
{

  {  
    int reading = digitalRead(StartButton);     
    if (reading != lastButtonState) 
     {
      lastDebounceTime = millis();
      }
  
    if ((millis() - lastDebounceTime) > debounceDelay) 
        {
        if (reading != buttonState) 
          {
            buttonState = reading;
  
            if (buttonState == LOW) {
            Starter = 1;
            }
          }
        }
    lastButtonState = reading;
  }

  {
  if (Starter = 1) ;
  
    {
      // int rpm1 = 0;
      if (time1 > 0)
        {
        rpm1 = 60 * ((1000000 / (time1)) / rpm1_k);
        }
      // int rpm2 = 0;
      if (time2 > 0)
        {
        rpm2 = 60 * ((1000000 / (time2)) / rpm2_k);
        }

  DateTime now;
  delay((LOG_INTERVAL - 1) - (millis() % LOG_INTERVAL));

  digitalWrite(greenLEDpin, HIGH);

  uint32_t m = millis();
  logfile.print(m);
  logfile.print(", ");

  now = RTC.now();
  logfile.print(now.year(), DEC);
  logfile.print("/");
  logfile.print(now.month(), DEC);
  logfile.print("/");
  logfile.print(now.day(), DEC);
  logfile.print(" ");
  logfile.print(now.hour(), DEC);
  logfile.print(":");
  logfile.print(now.minute(), DEC);
  logfile.print(":");
  logfile.print(now.second(), DEC);
  logfile.print('"');

  analogRead(TPS_pin);
 // delay(10);
  int TPSraw = analogRead(TPS_pin);
  TPS = map(analogRead(TPS_pin),TPS_Min, TPS_Max,0,100);

  analogRead(lambda_pin);
  //delay(10);
  int lambda = analogRead(lambda_pin) * (0.68/1023) +0.68;
  int AFR = lambda * 14.7;

  logfile.print(", ");
  logfile.print(TPS);
  logfile.print(", ");
  logfile.print(lambda);
  logfile.print(", ");
  logfile.print(AFR);
  logfile.print(", ");
  logfile.print(rpm1);
  logfile.print(", ");
  logfile.print(rpm2);
  logfile.print(", ");


  sensors_event_t event;
  accel.getEvent(&event);
  
  X2 = X1 - (event.acceleration.x);
  Y2 = Y1 - (event.acceleration.y);
  Z2 = Z1 - (event.acceleration.z);
  
  logfile.print(X2);
  logfile.print(", ");
  logfile.print(Y2);
  logfile.print(", ");
  logfile.print(Z2);


  // sensors_event_t event;
  // accel.getEvent(&event);

  // logfile.print(event.acceleration.x);
  // logfile.print(", ");
  // logfile.print(event.acceleration.y);
  // logfile.print(", ");
  // logfile.print(event.acceleration.z);

  logfile.println();


  digitalWrite(greenLEDpin, LOW);

  if ((millis() - syncTime) < SYNC_INTERVAL) return;
  syncTime = millis();

  // blink LED to show we are syncing data to the card & updating FAT!
  digitalWrite(redLEDpin, HIGH);
  logfile.flush();
  digitalWrite(redLEDpin, LOW);

}

{
//   lcd.setCursor(0, 0);
//   lcd.print("Live data");
  
//   int rpm1 = 0;
//   if (time1 > 0)
//   {
//     rpm1 = 60 * ((1000000 / (time1)) / rpm1_k);
//   }
//   int rpm2 = 0;
//   if (time2 > 0)
//   {
//     rpm2 = 60 * ((1000000 / (time2)) / rpm2_k);
//   }

//   analogRead(TPS_pin);
// // delay(10);
//   int TPSraw = analogRead(TPS_pin);
//   TPS = map(analogRead(TPS_pin),TPS_Min, TPS_Max,0,100);

//   analogRead(lambda_pin);
//   //delay(10);
//   int lambda = analogRead(lambda_pin) * (0.68/1023) +0.68;
//   int AFR = lambda * 14.7;

  // lcd.setCursor(0, 0);
  // lcd.print(TPS);
  // lcd.setCursor(0, 0);
  // lcd.print(lambda);
  // lcd.setCursor(0, 0);
  // lcd.print(AFR);
  // lcd.setCursor(0, 0);
  // lcd.print(rpm1);
  // lcd.setCursor(0, 0);
  // lcd.print(rpm2);
  
  
  // sensors_event_t event;
  // accel.getEvent(&event);
  
  // X2 = X1 - (event.acceleration.x);
  // Y2 = Y1 - (event.acceleration.y);
  // Z2 = Z1 - (event.acceleration.z);
  
  // lcd.setCursor(0, 0);
  // lcd.print(X2);
  // lcd.setCursor(0, 0);
  // lcd.print(Y2);
  // lcd.setCursor(0, 0);
  // lcd.print(Z2);
    
    
}
}
}

void rpm1()
{
  time1 = (micros() - time_last1);
  time_last1 = micros();
}
void rpm2()
{
  time2 = (micros() - time_last2);
  time_last2 = micros();
}

Here is the error text from the web editor.

./opt/arduino-builder/arduino-builder -compile -core-api-version 10611 -build-path /tmp/273568955/build -hardware opt/arduino-builder/hardware -hardware ./opt/cores -tools opt/arduino-builder/tools -tools ./opt/tools -built-in-libraries opt/libraries/latest -libraries /tmp/273568955/pinned -libraries /tmp/273568955/custom -fqbn arduino:avr:uno -build-cache /tmp -logger humantags -verbose=false /tmp/273568955/car_data_logger

Multiple libraries were found for "SD.h"

Used: /tmp/273568955/custom/SD

Not used: /tmp/273568955/custom/SD-e9779ff92157377500b0e00f3f89bf2913aa34ad

Not used: /home/ubuntu/opt/libraries/latest/sd_1_2_2

Multiple libraries were found for "Adafruit_LiquidCrystal.h"

Used: /tmp/273568955/custom/Adafruit_LiquidCrystal

Not used: /home/ubuntu/opt/libraries/latest/adafruit_liquidcrystal_1_0_0

Multiple libraries were found for "RTClib.h"

Used: /tmp/273568955/custom/RTClib

Not used: /home/ubuntu/opt/libraries/latest/rtclib_by_neiron_1_5_4

Not used: /home/ubuntu/opt/libraries/latest/rtclib_1_2_0

/tmp/273568955/car_data_logger/car_data_logger.ino: In function 'void rpm1()':

/tmp/273568955/car_data_logger/car_data_logger.ino:333:11: error: 'void rpm1()' redeclared as different kind of symbol

void rpm1()

^

/tmp/273568955/car_data_logger/car_data_logger.ino:48:14: note: previous declaration 'volatile int rpm1'

volatile int rpm1 ;

^

/tmp/273568955/car_data_logger/car_data_logger.ino: In function 'void rpm2()':

/tmp/273568955/car_data_logger/car_data_logger.ino:338:11: error: 'void rpm2()' redeclared as different kind of symbol

void rpm2()

^

/tmp/273568955/car_data_logger/car_data_logger.ino:49:14: note: previous declaration 'volatile int rpm2'

volatile int rpm2 ;

^

exit status 1

I'm not sure what it all means.
Thanks

First, it seems like it's digging some libraries out of your /temp folder. I'm not familiar with the standard Arduino installation on Linux but either you are compiling the sketch from that location or you should delete those temporary copies.

Then it's complaining that you have created an integer called rpm1 and a function called rpm1(). You must use different names for your variables and functions. rpm2 has the same problem. Use longer names. calculateRPM() is a good name for a function.

That did it!

Thank you so much!

I knew it would be something simple I missed!