Train Accelerometer Proof-of-Concept Arduino Code

Here’s the code used for the accelerometer reader and data writer to SD card for reference purposes:

 

// Train accelerometer measurement and data logging system
//
// (C) 2014 zapmaker with credit to others inline
// Original code from 2012
// Apache 2.0 license except as noted by others' code
//
#include <WProgram.h>
#include <avr/pgmspace.h>
#include <microfat2.h>
#include <mmc.h>
#include <DevicePrint.h>

#define srDataPin 2
#define srClockPin 3
#define demuxAPin 4
#define demuxBPin 5
#define demuxCPin 6
#define demuxEnablePin 7
#define tachPin 8

// Accelerometer that outputs three axes as a voltage
#define accelXPin A0
#define accelYPin A1
#define accelZPin A2

// Trim potentiometers
#define trimXPin A3
#define trimYPin A4
#define scalePin A5

DevicePrint dp;
byte sectorBuffer[512];
char sprint_buffer[40];

// filtering array to smooth out accelerometer data
#define arrSize 20
word arrX[arrSize];
word arrY[arrSize];
byte flipLookup[8];

word arrXPos = 0;
word arrYPos = 0;

/* Timer2 reload value, globally available */  
unsigned int tcnt2;
unsigned int timeVal = 0;

byte writeC = 0;

void setup()
{
  // 74LS164 shift register clock/data
  pinMode(srDataPin, OUTPUT);
  pinMode(srClockPin, OUTPUT);
  // 74LS138 demux data/control
  pinMode(demuxAPin, OUTPUT);
  pinMode(demuxBPin, OUTPUT);
  pinMode(demuxCPin, OUTPUT);
  pinMode(demuxEnablePin, OUTPUT);

  // photointerrupter input to measure wheel distance of train engine
  pinMode(tachPin, INPUT);  

  digitalWrite(srDataPin, LOW);  
  digitalWrite(srClockPin, LOW);
  digitalWrite(demuxAPin, LOW);
  digitalWrite(demuxBPin, LOW);
  digitalWrite(demuxCPin, LOW);
  digitalWrite(demuxEnablePin, LOW);

  Serial.begin(115200);

  if (mmc::initialize() != RES_OK)
    error(PSTR("mmc init failed.\n"));

  // Pass in the sector-sized buffer we'll be using ourselves later.
  // uFat doesn't own it, it just needs to use it temporarily.
  // We also pass in the address of a function that is used to read
  // sectors from our device.
  if (!microfat2::initialize(sectorBuffer, &mmc::readSectors))
    error(PSTR("uFat init failed.\n"));

  showDirectory();

  testWrite();

  unsigned long sector, fileSize;

  // the file name we are writing to the SD card
  if(!microfat2::locateFileStart(PSTR("DATA CSV"), sector, fileSize))
  {
    error(PSTR("Couldn't find data.csv on the card"));
  }

  // The dp library won't write past the end of the allowed space.
  // Indeed, it will only write up to the nearest sector boundary at
  // the end of the file on the card. If your target file is 1000 bytes,
  // only 512 will be written over. The remaining will be left as they
  // were when written to the card.

  dp.initialize(sector, fileSize / 512, sectorBuffer, proxyWriter);

  memset(sectorBuffer, '.', 512);   // fills the file on the card

  for (byte i = 0; i < arrSize; i++)
  {
    arrX[i] = 0;
    arrY[i] = 0;
  }

  // clear s/r
  for (byte i = 0; i < 8; i++)
  {
    digitalWrite(srClockPin, HIGH);
    digitalWrite(srClockPin, LOW);
  }

  byte initVal = 7;
  for (byte i = 0; i < 8; i++, initVal--)
  {
    flipLookup[i] = initVal;
  }

  setupTimerInterrupt();
}

void loop()
{
  int x, y, z=0;
  int xp, yp, sc;

  x = analogRead(accelXPin);
  arrX[arrXPos++] = x;
  if (arrXPos >= arrSize)
    arrXPos = 0;  

  // filter x-data 
  int xr = 0;
  for (byte i = 0; i < arrSize; i++)
    xr += arrX[i];
  xr /= arrSize;

  y = analogRead(accelYPin);
  arrY[arrYPos++] = y;
  if (arrYPos >= arrSize)
    arrYPos = 0;

  // filter y-data 
  int yr = 0;
  for (byte i = 0; i < arrSize; i++)
    yr += arrY[i];
  yr /= arrSize;

  int trimsc = 16;
  word inXTr, inYTr, inSc;

  // read in the current trim values
  inXTr= analogRead(trimXPin);
  inYTr = analogRead(trimYPin);
  inSc = analogRead(scalePin);

  // make the trim adjustments
  xp = inXTr / trimsc;
  yp = inYTr / trimsc;
  sc = (inSc / 32) + 1;

  // compute a value 0-7 for the 8x8 LED matrix
  int xf = ((xr - ((366 + (xp - (trimsc / 2))) - (4 * sc))) / sc);
  int yf = ((yr - ((369 + (yp - (trimsc / 2))) - (4 * sc))) / sc);
  //z = analogRead(accelZPin);

  // read the wheel position
  int tachState = digitalRead(tachPin);

  writeLedMatrix(xf, yf);

  if (writeC == arrSize)
  {
    // write data to the SD card
    writeData(xr, yr, tachState);
    writeC = 0;
  }

  writeC++;
}

void writeLedMatrix(int dc, int sv)
{
  if (dc < 0)
  {
    dc = 0;
  }
  else if (dc > 7)
  {
    dc = 7;
  }
  else if (sv < 0)
  {
    sv = 0;
  }
  else if (sv > 7)
  {
    sv = 7;
  }  

  // this is to save on pins used by the Arduino - let two external chips drive the matrix
  digitalWrite(demuxEnablePin, LOW);
  writeDemux(dc);
  writeShifter(sv);      
  digitalWrite(demuxEnablePin, HIGH);
}

void writeLedMatrixWithOverflow(int dc, int sv)
{
  word d = 2;
  byte c = 3;
  boolean hit = false;
  if (dc < 0)
  {
    dc = 0;
    hit = true;
    for (byte j = 0; j < c; j++)
      for (byte i = 0; i < 8; i++)
      {
        digitalWrite(demuxEnablePin, LOW);
        writeDemux(dc);
        writeShifter(i);      
        digitalWrite(demuxEnablePin, HIGH);
        delay(d);
      }  
  }
  else if (dc > 7)
  {
    dc = 7;
    hit = true;
    for (byte j = 0; j < c; j++)
      for (byte i = 0; i < 8; i++)
      {
        digitalWrite(demuxEnablePin, LOW);
        writeDemux(dc);
        writeShifter(i);      
        digitalWrite(demuxEnablePin, HIGH);
        delay(d);
      }  
  }
  else if (sv < 0)
  {
    sv = 0;
    hit = true;
    for (byte j = 0; j < c; j++)
      for (byte i = 0; i < 8; i++)
      {
        digitalWrite(demuxEnablePin, LOW);
        writeDemux(i);
        writeShifter(sv);      
        digitalWrite(demuxEnablePin, HIGH);
        delay(d);
      }  
  }
  else if (sv > 7)
  {
    sv = 7;
    hit = true;
    for (byte j = 0; j < c; j++)
      for (byte i = 0; i < 8; i++)
      {
        digitalWrite(demuxEnablePin, LOW);
        writeDemux(i);
        writeShifter(sv);      
        digitalWrite(demuxEnablePin, HIGH);
        delay(d);
      }  
  }  
  else
  {
    digitalWrite(demuxEnablePin, LOW);
    writeDemux(dc);
    writeShifter(sv);      
    digitalWrite(demuxEnablePin, HIGH);
  }
}

void writeDemux(word value)
{
  word newValue = value < 0 ? 0 : value;
  newValue = value > 7 ? 7 : newValue;

  newValue = flipLookup[newValue];
  newValue <<= 4;

  PORTD = (0x8F & PORTD) | (newValue);
}

void writeShifter(word value)
{
  word newValue = value < 0 ? 0 : value;
  newValue = value > 7 ? 7 : newValue;  

  for (byte i = 0; i < 8; i++)
  {
    if (i == newValue)
      digitalWrite(srDataPin, HIGH);
    else
      digitalWrite(srDataPin, LOW);

    digitalWrite(srClockPin, HIGH);
    digitalWrite(srClockPin, LOW);
  }
}

// BEWARE - don't print strings longer than 39 characters!
//          If you can't help it, adjust buffer size above.
//
void pprint(const char* s)
{
 strcpy_P(sprint_buffer, (PGM_P)s);
 Serial.print(sprint_buffer);
}

void error(const char* s)
{
 pprint(PSTR("Error: "));
 pprint(s);
 pprint(PSTR("<press reset>"));
 for( /* ever */ ; ; )
 {
   digitalWrite(13, (millis() / 250) & 1);
 }
}

//http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1235125412/0
bool showDirectory_walkerfn(directory_entry_t* directory_entry_data, unsigned index, void* user_data)
{
 int* count = (int*)user_data;

 Serial.print(index, DEC);
 Serial.print(' ');

 // Terminate the filename string. 
 // This is deliberately corrupting the buffer data, but that's ok.
 directory_entry_data->filespec[11] = 0;

 Serial.println(directory_entry_data->filespec);

 // Increase 'seen file' count
 *count = (*count)+1;

 // don't stop
 return false;
}

void showDirectory(void)
{
 int count = 0;

 pprint(PSTR("Directory of files on card:\n\n"));

 microfat2::walkDirectory(showDirectory_walkerfn, &count);

 pprint(PSTR("\n"));
 Serial.print(count, DEC);
 pprint(PSTR(" files found.\n\n"));
}

void testWrite()
{
 unsigned long sector;
 unsigned long byteSize;

 if (microfat2::locateFileStart(PSTR("DATA CSV"), sector, byteSize))
 {
   if (byteSize >= 512)
   {
     if (RES_OK == mmc::readSectors(sectorBuffer, sector, 1))
     {
       for (int i = 0; i < 512; ++i)
       {
         sectorBuffer[i] = sectorBuffer[i] + 1;
       }
       if (RES_OK == mmc::writeSectors(sectorBuffer, sector, 1))
       {
         pprint(PSTR("Written to data.csv OK!"));
       }
       else
       {
         error(PSTR("Failed to write updated data."));
       }
     }
     else
     {
       error(PSTR("Failed to read data.csv."));
     }
   }
   else
   {
     error(PSTR("Found data.csv, but it's too small."));
   }
 }
 else
 {
   error(PSTR("data.csv not present on card."));
 }
}

void writeData(int xp, int yp, int tachState)
{
  dp.print(timeVal);
  dp.print(",");  
  dp.print(xp);  
  dp.print(",");
  dp.print(yp);
  dp.print(",");
  dp.print(tachState);
  dp.println();
}

// https://docs.google.com/Doc?id=dqhc6fg_0gmk96kdd&pli=1
uint8_t proxyWriter(const uint8_t* buffer, unsigned long sector, uint8_t count)
{
  // I could have just used this function to pass as the sector write...
  //
  uint8_t val = mmc::writeSectors(buffer, sector, count);

  // ... but I want to process the buffer after each write to the card!
  //
  if (dp.m_bufferIndex == 512)
  {
    // we've written a full buffer so clear it out ready for the next
    // writes - the device print variables will be updated after we return
    // from this function.
    //
    memset(sectorBuffer, '.', 512);
  }

  return val;
}

// http://popdevelop.com/2010/04/mastering-timer-interrupts-on-the-arduino/
void setupTimerInterrupt()
{
    /* First disable the timer overflow interrupt while we're configuring */  
   TIMSK2 &= ~(1<<TOIE2);  

   /* Configure timer2 in normal mode (pure counting, no PWM etc.) */  
   TCCR2A &= ~((1<<WGM21) | (1<<WGM20));  
   TCCR2B &= ~(1<<WGM22);  

   /* Select clock source: internal I/O clock */  
   ASSR &= ~(1<<AS2);  

   /* Disable Compare Match A interrupt enable (only want overflow) */  
   TIMSK2 &= ~(1<<OCIE2A);  

   /* Now configure the prescaler to CPU clock divided by 128 */  
   TCCR2B |= (1<<CS22)  | (1<<CS20); // Set bits 
   TCCR2B &= ~(1<<CS21);             // Clear bit 

   /* We need to calculate a proper value to load the timer counter. 
    * The following loads the value 131 into the Timer 2 counter register 
    * The math behind this is: 
    * (CPU frequency) / (prescaler value) = 125000 Hz = 8us. 
    * (desired period) / 8us = 125. 
    * MAX(uint8) + 1 - 125 = 131; 
    */  
   /* Save value globally for later reload in ISR */  
   tcnt2 = 131;   

   /* Finally load end enable the timer */  
   TCNT2 = tcnt2;  
   TIMSK2 |= (1<<TOIE2);
}

/* 
 * Install the Interrupt Service Routine (ISR) for Timer2 overflow. 
 * This is normally done by writing the address of the ISR in the 
 * interrupt vector table but conveniently done by using ISR()  */  
ISR(TIMER2_OVF_vect) {  
  /* Reload the timer */  
  TCNT2 = tcnt2;  
  timeVal++;
}

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>