Rotary Encoder and SD Card Skipping Counts

Hi everyone,

I'm using a magnetic rotary encoder for a project that involves counting the "encoder ticks" and the time each one occurs at in the positive and negative direction. There's been no problem with that part of the code, works as intended. But I'm having a problem when I try to store the values for position and time on an SD card (Class 10) (using adafruit Micro SD Card Breakout Board).

No matter the speed I turn the encoder, there's skipping when it writes to the SD card. I'm assuming it s to do with the buffer?

#include "BlockDriver.h"
#include "FreeStack.h"
#include "MinimumSerial.h"
#include "SdFat.h"
#include "SdFatConfig.h"
#include "sdios.h"
#include "SysCall.h"
#include <SPI.h>
#include "Arduino.h"
#include "digitalWriteFast.h" 

SdFat SD;
 
#define c_EncoderInterrupt 0
#define d_EncoderInterrupt 1

#define c_EncoderPinA 2
#define c_EncoderPinB 3

#define EncoderIsReversed
volatile bool _EncoderBSet;
volatile long _EncoderTicks = 0;
unsigned long Time;
long copy_EncoderTicks = 0; //new variable for protected copy

#define FILE_BASE_NAME "Dat"
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
char fileName[] = FILE_BASE_NAME "00.dat";

// SdFat has long filenames

File dataFile;

void setup()
{
  Serial.begin(250000);
  Serial.print("Initializing SD card...");
  pinMode(10, OUTPUT);
   
  if (SD.begin(10))
  {
    Serial.println("SD card is ready to use.");
  } else
  {
    Serial.println("SD card initialization failed");
    return;
  }
  while (SD.exists(fileName)) {
    if (fileName[BASE_NAME_SIZE + 1] != '9') {
      fileName[BASE_NAME_SIZE + 1]++;
    } else if (fileName[BASE_NAME_SIZE] != '9') {
      fileName[BASE_NAME_SIZE + 1] = '0';
      fileName[BASE_NAME_SIZE]++;
    } else {
      Serial.println(F("Can't create file name"));
      return;
    }
  }

  // Quadrature encoder
  pinMode(c_EncoderPinA, INPUT); // sets pin A as input
  digitalWrite(c_EncoderPinA, LOW); // turn on pullup resistors
  pinMode(c_EncoderPinB, INPUT); // sets pin B as input
  digitalWrite(c_EncoderPinB, LOW); // turn on pullup resistors

  attachInterrupt(c_EncoderInterrupt, HandleMotorInterruptAfirst, RISING);

}

void loop()
{
  noInterrupts();
  copy_EncoderTicks = _EncoderTicks;
  interrupts();
  writeSD();

  delay(10);
}

void HandleMotorInterruptAfirst()
{
  // Test transition; since the interrupt will only fire on 'rising' we don't need to read pin A
  _EncoderBSet = digitalReadFast(c_EncoderPinB); // read the input pin
  Time = millis();

  // and adjust counter + if A leads B
#ifdef EncoderIsReversed
  _EncoderTicks -= _EncoderBSet ? -1 : +1;
#else
  _EncoderTicks += _EncoderBSet ? -1 : +1;
#endif
}

void writeSD(){

  dataFile = SD.open(fileName, FILE_WRITE);
  
 
  if (dataFile)
  {
    dataFile.print(copy_EncoderTicks);
    dataFile.flush();
    //Serial.print(copy_EncoderTicks);
    dataFile.print(",");
    dataFile.flush();
    //Serial.print(",");
    dataFile.print(Time);
    dataFile.flush();
    //Serial.print(Time);
    dataFile.println();
    //Serial.println();
    dataFile.flush();
    //dataFile.close(); // close the file:
    //Serial.println("data written\n\n");
  
  } else
  {
    // if the file didn't open, report an error:
    //Serial.println("\nerror opening the text file!");
  }
}

I've tried using the sdfat library, saving as dat files instead of text since it's suppose to be faster and using the flush command at different points but none of them seems to work. I know high class SD cards can be a problem but I don't know if that's the case here.

I'd appreciate any help/advice about what I might be doing wrong or if there's a better method of saving to the SD card without loss. The encoder doesn't run for a long time <1minute. Could I save to the arduino's eeprom and then to an SD card?

Thanks!

Are you trying to record every tick on the SD card?

If so, that delay isn't helping. You might try leaving the file open too, opening it for every write is costing you time I expect. Forget the flush too.

Those things will help, but I suspect you'll need to buffer the data in RAM and then write it if you want to keep up with the encoder.

Here's a sample sketch to help you out.

As mentioned, you'll need a buffer. Not obvious in this example is that the serial data is buffered via an interrupt in the serial library. The main loop() is pulling data out of the serial buffer and filling another buffer for writing to the SD card. When the card buffer gets to a certain size (100) it writes it to the card. I would probably change the value to 128, though, as it is less wear on the SD card sectors (4 writes versus 6). Although, if you can't afford to lose much data then change that value to your liking.

/*
   This Arduino sketch will log GPS NMEA data to a SD card every second
*/

#include <SoftwareSerial.h>
#include <SD.h>


// The Arduino pins used by the GPS module
static const int GPS_RXPin = A1;
static const int GPS_TXPin = A0;
static const int GPSBaud = 9600;
static const int chipSelect = 10;

// The GPS connection is attached with a software serial port
SoftwareSerial Gps_serial(GPS_RXPin, GPS_TXPin);


void setup()
{    
  // Open the debug serial port at 9600
  Serial.begin(9600);
 
  // Open the GPS serial port  
  Gps_serial.begin(GPSBaud);  
   
  Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");   
}


int inByte = 0;         // incoming serial byte
byte pbyGpsBuffer[100];
int byBufferIndex = 0;

void loop()
{
  byte byDataByte;
  
  if (Gps_serial.available())
  {
     byDataByte = Gps_serial.read();
    
     Serial.write(byDataByte);
     pbyGpsBuffer[ byBufferIndex++ ] = byDataByte;
     
     if( byBufferIndex >= 100 )
     {
       byBufferIndex = 0;       
       File dataFile = SD.open("gps.txt", FILE_WRITE);
    
       // if the file is available, write to it:
       if (dataFile) {
        dataFile.write(pbyGpsBuffer, 100);
        dataFile.close();
      }  
      // if the file isn't open, pop up an error:
      else {
        Serial.println("error opening gps.txt");
      }        
     }      
  }
}

Thanks very much wildbill and MK1888.

Yes I do want to record the every tick on the SD card, for the length of time the encoder is turning for I really want to avoid any loss. I'll fix it up and try the buffer and post back how it goes.

If you have enough ram for two 512-byte buffers, the encoder interrupts could save data into the first buffer, then switch to the other buffer. Meanwhile, your Loop would notice that the first buffer is filled, and write its contents to the SD card - hopefully before the other buffer has been filled. My understanding is that SD and SPI activity is interruptible without causing problems. That's one advantage of having a clock signal driven by the processor. Anyway, that kind of setup should eliminate lost decoder readings.

Hi,

Thanks for the advice. I got rid of the delay and have the sd card opening in setup which helped a bit but the encoder still gives negative values when it's rotated in the positive direction at normal speeds.

I've never worked with the buffer below so I'm a novice with it. I adapted the few lines of code from MK1888 but the board I'm using is the Uno so I don't think I have enough RAM to store in one part of the buffer before writing to the sd card, or maybe I'm misunderstanding it? I'm struggling to put it into practice

The Uno only has 2K of ram, so probably not enough for all the buffers. Well you have kind of a fundamental problem with the time it takes to write to an SD card. I don't know if there's a solution for that.

The code I posted above logs data from a GPS to a SD card using an Uno. GPS data is 9600 bps, which is likely way faster than anything you're doing with your encoder.