Introduction: Arduino Based Time-event Logger

About: Degrees in EE, specializing in Digital Signal Processing. Working as a software engineer for 30+ years.

Example source code for an Arduino based time-event data logger.  Demonstrates interrupts, asynchronous logging, and contact de-bounce.  Intended for a wheel position switch, logs time at which switch is engaged.

We are using a reed switch which bounces both when engaged, and when dis-engaged.  This throws multiple interrupts.  Ring buffer input checks if time event period is vastly shorter than previously logged time event.  If so, short trigger is rejected as contact bounce.  Rejected events are stored, and if many rejects occur in a row, they are all logged, assuming that something happened and they may be significant events.  The motivation is that it is better to log a few bounces than to miss an event for this application.  The data can be farther cleaned up in post processing, but we do not want to clutter our logs with ALL of the contact bounce events.

/*
$URL: svn+ssh://aaron@birenboim.com/home/svn/arduino/sketchbook/vLogger/vLogger.ino $
$Id: vLogger.ino 62 2012-06-03 20:11:13Z aaron $

Log the times when a wheel position sensor triggers,
as an indirect method of logging time and distance,
which can be used to estimate velocity and acceleration
in a coast-down wind resistance experiment.

User flips switch to record to a ring buffer.
Main loop prints ring buffer contents to serial port, asynch.

Each time the record operation cycles, the time is reset to
start at zero.

Sensor switch must be on pin 2, since this pin uses
standard external interrupt 0
*/

#define PIN_RECORD 4   // record on/off switch
#define PIN_LED_RECORD 8  // on when recording
#define PIN_LED_SENSOR 13 // toggles as sensor turns

#define BUF_LEN 256 // samples in data ring buffer
#define MAX_SHORT 10 // max "short" events before assuming they are NOT contact bounce

typedef long DATATYPE;

#include // sprintf

// Diagnostic output logger.  Call log from ISR, print outside ISR
#define MAX_LOG_LEN 40
#define LOG_BUF_LEN  8
class Logger {
protected:
  volatile int nIn;
  int nOut;
  char buf[LOG_BUF_LEN][MAX_LOG_LEN];
public:
  char msgBuf[MAX_LOG_LEN];
  Logger() { nIn=nOut=0; }
  void print()
  {
    if (nOut >= nIn) return;
    int i = nOut % LOG_BUF_LEN;
    nOut++;
    Serial.println(buf[i]);Serial.flush();
  }
  void log(const char *msg)
  {
    int i,k;
    k = nIn % LOG_BUF_LEN;
    for (i=0; (msg[i]!=0) && (i < MAX_LOG_LEN-1); i++) buf[k][i]=msg[i];
    buf[k][i]=0;
    nIn++;
  }
  void log() { log(msgBuf); } // for using provided msgBuf
} Log;

class RingBuffer {
protected:
  volatile int nIn;
  int nOut;
  volatile DATATYPE buf[BUF_LEN];

public:
  void reset() { nIn=nOut=0; }
  RingBuffer() { reset(); }

  volatile void push(DATATYPE x)
  {
    if (nOut <= nIn - BUF_LEN)
      {
        sprintf(Log.msgBuf,"RingBuffer Overflow %d",x);Log.log();
        //Serial.println("RingBuffer Overflow ");Serial.println(x);//Serial.flush();
        return;
      }
    int i = nIn % BUF_LEN;
    buf[i]=x;
    nIn++;
  }
  inline bool empty() const { return(nOut >= nIn); }
  DATATYPE pop()
  {
    if (nOut >= nIn) return(0xffff);
    int i = nOut % BUF_LEN;
    nOut++;
    return(buf[i]);
  }
  DATATYPE peek() // peek at last OUTPUT
  {
    int i = nOut % BUF_LEN;
    return(buf[i]);
  }
  DATATYPE peek(const int nBack) // peak at indicated prev INPUT, 0 is most recent
  {
    int i=nIn-1-nBack;
    if (i < 0) return 0;
    return(buf[i]);
  }
  inline int nPush() const { return(nIn); }
  inline int nPop()  const { return(nOut);}
  inline int depth() const { return(nIn-nOut); }
  inline int maxDepth() const { return(BUF_LEN); }
};

class TimeEventRingBuffer : public RingBuffer {
protected:
  unsigned long t0;
  volatile int nShort;
  volatile DATATYPE shortBuf[MAX_SHORT];
public:
  void reset()
  {
    setZero();
    nShort=0;
    RingBuffer::reset();
  }
  TimeEventRingBuffer()
  {
    nShort=t0=0;
  }
  void push()
  {
    unsigned long tt = millis();
    DATATYPE tNow = (DATATYPE)(tt-t0);

    // check for contact bounce
    DATATYPE t1 = peek(0);  // prev recorded input
    DATATYPE t2 = peek(1);
    if ( tNow-t1 < 0.1 * (t1-t2) )
      { // short trigger, likely contact bounce
        if (nShort < MAX_SHORT)
          {
            shortBuf[nShort++]=tNow;
            return;
          }
        // Too many short triggers in a row, dump them
        for (int i=0; i < MAX_SHORT; i++) RingBuffer::push(shortBuf[i]);
      }

    // record the time of this event
    RingBuffer::push(tNow);
    nShort=0;  // this event was good. reset short-trigger counter
  }
  void setZero()
  {
    t0 = millis();
  }
};

TimeEventRingBuffer Data;
bool RecordingOn;

// ISR when wheel sensor event is detected
void wheelSense()
{
  if (RecordingOn)
    {
      Data.push();
    }
  digitalWrite(PIN_LED_SENSOR,digitalRead(PIN_LED_SENSOR)?LOW:HIGH);
}

void setup()
{
  pinMode(2,INPUT);  // hard-coded to pin 2, since using standard ext. interrupt 0
  pinMode(PIN_RECORD,INPUT);
  digitalWrite(PIN_RECORD,HIGH);  // enable pull-up resistor
  digitalWrite(2,HIGH);

  pinMode(PIN_LED_RECORD, OUTPUT);
  pinMode(PIN_LED_SENSOR,  OUTPUT);
  digitalWrite(PIN_LED_RECORD,HIGH);
  digitalWrite(PIN_LED_SENSOR,HIGH);

  RecordingOn = true;//false;
  attachInterrupt(0,wheelSense,RISING);  // INT0 is on pin 2 (INT1 on pin 3)

  Serial.begin(9600);  // may want a different serial monitor, which can be faster
}

void loop()
{
  Log.print();  // check if any log messages need to be printed
  
  if (!Data.empty())
    {
      Serial.print(Data.nPop());
      Serial.print("\t");
      Serial.println(Data.pop());
      Serial.flush();
    }

  // check if recording is currently enabled
  bool rec = (digitalRead(PIN_RECORD)==LOW)?true:false;
  //rec=true;// always record for testing

  if (RecordingOn)
    {
      if (!rec)
        { // turn recording OFF
          digitalWrite(PIN_LED_RECORD,LOW);
          RecordingOn = false;
          delay(2);  // avoid contact bounce
        }
    }
  else
    { // recording is off
      if (rec)
        { // transition from off to on, start recording
          digitalWrite(PIN_LED_RECORD,HIGH);
          Data.setZero();
          if (Data.empty()) Data.reset();
          RecordingOn = true;
          delay(2);  // avoid contact bounce
        }
    }
}


Here is a convenient LINUX script to both display and log the data from a serial connection. This code is set for a USB/Serial port connection, but if you change the device it looks for to something like /dev/rfcomm0 it can be set up to work over a Bluetooth/SPP device.
#!/usr/bin/perl -w
# Logs output from an arduino at typical 9600 8-n-1

# assume most recently created tty is the Arduino.
# for most users, this should usually work
$devName = `ls -t /dev/tty* | head -1`;
chomp($devName);
print "Listening on $devName\n";

# set up port for typical Arduino (SerialMonitor-like) settings
system("stty -F $devName cs8 9600 ignbrk -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke noflsh -ixon -crtscts");

$logFileName = ($#ARGV>=0) ? $ARGV[0] : "/tmp/serial.log";
print "Logging to $logFileName\n";

$pid = fork();
if ($pid==0)
  {
    # this is the child.
    local $cmd = "tail -f $devName > $logFileName";
    # for some reason, the above did not work on a netbook, but this did:
    #local $cmd = "cat $devName > $logFileName";
    print "$cmd\n";
    exec($cmd);
  }
else
  {
    # this is the parent, display log.
    # hopefully when this is ^C'ed or killed, it will kill child too
    sleep 2;
    local $cmd = "tail -f $logFileName";
    print "$cmd\n";
    exec($cmd);
  }