Time sensitive interrupts/digital reads

I am currently working on a project that uses a self clocking TTL interface, but am having problems reading the data reliably. If I slow the data and the clock down significantly, everything works as it should. This eliminates hardware and the logic part of my code as being at fault, I would think. However, at "normal" speeds, the data input becomes unreliable.

So, for example, I have the clocking signal attached to Pin 2 of my Duemilanove (ATMega328) with

attachInterrupt(0, functionToRun, FALLING);

and the function simply reads the current value of the data pin and records it using digitalRead. After doing some research with Google, I found that the digitalRead was relatively slow and could cause problems with a time sensitive application. I also found the digitalWriteFast library that uses Port Manipulation, and gave that a try, but found that it suffered from the same problems.

This has lead me to the possibility that the interrupt can not be triggered/executed fast enough. Would anyone else agree with this? I saw that there were some ways to attach interrupts directly using AVR registers, but I'm not sure if this would speed anything up.

Basically, I am asking for any suggestions or ideas on what you would consider to be the fastest, or most accurate, way to read this data.

For reference, I am currently just using a loop to check the state of Pin 2, and if it is low, read the data pin. This also only works if the data/clock is relatively slow (but that is expected since I wouldn't think it is very efficient).

How fast before problems show up? Microsecs? Nanosecs?

I think the best first step is to post your code, or code small enough to exibit the problem. There may be some other blocking/timing constrain involved that you aren't aware of.

Lefty

It's difficult to say exactly at what speed problems occur, but if I would have to venture a guess I would say anything greater than around 100 interrupts per second causes problems.

code:

void setup()
{
  pinMode(7, INPUT);
  attachInterrupt(0, readData, FALLING);
  attachInterrupt(1, finishedReading, RISING);
  Serial.begin(9600);
}

String buffer = "";
boolean output = false;

void loop()
{
  if(output)
  {
    Serial.println(buffer);
    buffer = "";
    output = false;
  }
}

void readData()
{
      buffer += !digitalRead(7);
}

void finishedReading()
{
  output = true;
}

I could always invert the data after the reading is finished, I guess. I'm not sure how many cycles that takes but I would imagine a binary flip would only be 1.

  1. Check the reference, you are supposed to make all variables modified in interrupt routines volatile, which tells the compiler that they may be altered without notice and stops some optimisation.

  2. What are you trying to do with the read? A string assigned to "" is going to be a single byte with a 0x00 in it, i.e. same as myarr char[0]; The read is incrementing the contents of the buffer, so after the first read the contents of char[0] is going to be 1. That's not a terminated string, so whatever you are trying to write as a string is going to be wrong.

Any global variable that is going to be used in a ISR must be declared volatile:

volatile boolean output = false;

Not sure about your String object usage as I've never played around with it.

Lefty

The string just seemed like a very simple way to concatenate the reads, but now that I think about it, string concatenation usually has an efficiency of O(n). So I'm essentially copying the entire string and adding the new read to the end of it every time. I could use a fixed length array which should have an efficiency of O(1), but there is no way to know the length of the data beforehand.

To clarify, the end result of this is to have an array of bits.

I tend to have the ISR read into a ringbuffer. Then this can be read out at leisure.
The code below is partial from a sketch that had no problems reading a bitstream at 4800baud,
using interrupt driver clocking.

I am pretty sure that if you persist with writing into a string you need to declare it as a char buffer, then have a pointer into it and write the value using the pointer, incrementing the pointer.
Remember that c strings have a 0 termination, which is not automatic.

ringbuffer.h

/* Class definition
 ring buffer for storing input characters. These are the serial input
 ASCII characters 
 */

class RingBuffer {
  private:
  const int RBHILIM;
  const int RBLOLIM;
  
public:
  RingBuffer(char *extbuf, int exbufsize);
  void put_char(char c); // puts a char into the buffer
  char get_char();       // returns a char, removing from the buffer
  char peek_char();      // returns top char in buffer, no removol
  
  inline int  current_contents() 
  { 
    return ccount; 
  }; 
  
  int get_array_filled(char *carray, int arrsize);
  void clear_contents();
  bool at_highwatermark();
  bool at_lowwatermark();
  
  char diag_print(int k);
  int get_capacity() { return capacity; };

private:
  volatile char *buffer;
  volatile int stpos;
  volatile int endpos;
  volatile int ccount;
  const int capacity;

}; // end class definition

ringbuffer.cpp

#include <avr/interrupt.h>
#include "ringbuffer.h"

RingBuffer::RingBuffer(char *extbuf, int exbufsize) : 
capacity(exbufsize), RBHILIM(exbufsize *7/10), RBLOLIM(exbufsize *2/10), buffer(extbuf)
{
  stpos = endpos = 0;
  clear_contents();
}

void RingBuffer::clear_contents()
{
  uint8_t sreg = SREG;
  cli();
  // this function is security enforcing
  for (int i =0 ; i < capacity; i++)
  {
    buffer[i] = 'X';
  }
  ccount = 0;
  SREG = sreg;
}


int RingBuffer::get_array_filled(char *carray, int arrsize)
{
  // fills an array with ringbuffer contents
  // returns number of characters copied
  int pos = stpos;
  int tocopy = ccount;
  int i;
  if (arrsize-1 < tocopy)
  {
    tocopy = arrsize-1;
  };
  for (i = 0 ; i < tocopy ; i++)
  {
    carray[i] = buffer[pos];
    pos++;
    if (pos >= capacity)
    {
      pos = 0;
    }
  }
  carray[i] = 0;
  return tocopy; 
}


char RingBuffer::peek_char() { 
  return buffer[stpos]; 
}

void RingBuffer::put_char(char c)
{
  uint8_t sreg = SREG;
  cli();
  if (ccount < capacity)
  {
    ccount ++;
    buffer[endpos++] = c;
    if (endpos >= capacity)
    {
      endpos = 0;
    }
  }
  SREG = sreg;
}
;

char RingBuffer::get_char()
{
  char c;
  if (ccount > 0) {
    uint8_t sreg = SREG;
    cli();
    ccount --;
    c = buffer[stpos++];
    if (stpos >= capacity)
    {
      stpos = 0;
    }
    SREG = sreg;
    return c;
  }
  return 'X';
}



bool RingBuffer::at_highwatermark()
{ 
  return (ccount > RBHILIM);
} 

bool RingBuffer::at_lowwatermark()
{
  return (ccount < RBLOLIM);
} 


char RingBuffer::diag_print(int k)
{
  return buffer[k];
}
// end class declarations

Declaration of the rx ringbuffer:

#define INRB_SIZE 12
char sample_buffer[INRB_SIZE];
RingBuffer sample_rb(sample_buffer, INRB_SIZE);
#undef INRB_SIZE

ISR to read into a ringbuffer:

// this routine is an ISR
void rx_clockRise()
{

  byte portval = (PIND >> 2);
  if (portval & 0x04) // checking for PTRR
  {
    sample_rb.put_char(portval);
  }
  else
  {
    receive_synch_state = RX_NOT_SYNCH; 
  }

}

That's really fantastic stuff. I'll have to look over it to really figure out what all is going on, but thank you.

The string library is almost certainly your problem. Having to allocate memory every time you add an extra byte is going to slow down dramatically after a while. Considering you only have 2Kb of RAM anyway, you need to make up your mind what is the maximum you expect. The concatenation approach, for example, would run out of memory at 1Kb even with perfect garbage collection or memory reclamation, as it would need the old string (1Kb) and the new string (1Kb) in memory at the same time, as it made the copy. So you may as well bite the bullet and just allocate a buffer for the size you think you need and that you can spare.

The ring buffer approach is nice in the sense that you can be dealing with the old data (eg. sending it down a serial port) while the interrupts are firing and still filling the buffer up, as long as you (partly) empty it before it completely fills up.

BiohazrD:
I am actually also building one of these, although I have had different problems (reading the incoming data fast enough). Would you mind posting all of your code somewhere? I’d like to see how you are doing things to give me some insight to my project.

Hey BiohazrD,

You posted in my thread, and mentioned you were working with a card reader too? Here is my code, I got it from this instructable: http://www.instructables.com/id/Arduino-magnetic-stripe-decoder/ and make tweaks to fit it on an ATMEGA168. There is definitely a better way to do the bit to decimal conversion as well. but it work very nicely with a Omron V3A-4K, but Im sure it would work fine with any 2-track one-way card reader with a clock/data/card_present pin. Here is my code, you don’t need to have both interrupts and the bit operations could be reduced down but the following works well:

The bit conversion is based on the following:

[size=8pt]                       
** ANSI/ISO BCD Data format **

This is a 5-bit Binary Coded Decimal format.  It uses a 16-character set, which
uses 4 of the 5 available bits.  The 5th bit is an ODD parity bit, which means
there must be an odd number of 1's in the 5-bit character..the parity bit will
"force" the total to be odd.  Also, the Least Significant Bits are read FIRST
on the strip.  See Figure 6.

The sum of the 1's in each case is odd, thanks to the parity bit.  If the read
system adds up the 5 bits and gets an EVEN number, it flags the read as ERROR,
and you got to scan the card again (I *know* a lot of you out there *already*
understand parity, but I got to cover all the bases...not everyone sleeps with
their modem and can recite the entire AT command set at will, you know).  See
Figure 6 for details of ANSI/ISO BCD.

Figure 6:        ANSI/ISO BCD Data Format
---------

 * Remember that b1 (bit #1) is the LSB (least significant bit)!
  * The LSB is read FIRST!
  * Hexadecimal conversions of the Data Bits are given in parenthesis (xH).

        --Data Bits--   Parity
        b1  b2  b3  b4   b5    Character  Function

        0   0   0   0    1        0 (0H)    Data
        1   0   0   0    0        1 (1H)      "
        0   1   0   0    0        2 (2H)      "
        1   1   0   0    1        3 (3H)      "
        0   0   1   0    0        4 (4H)      "
        1   0   1   0    1        5 (5H)      "
        0   1   1   0    1        6 (6H)      "
        1   1   1   0    0        7 (7H)      "
        0   0   0   1    0        8 (8H)      "
        1   0   0   1    1        9 (9H)      "
        0   1   0   1    1        : (AH)    Control
        1   1   0   1    0        ; (BH)    Start Sentinel
        0   0   1   1    1        < (CH)    Control
        1   0   1   1    0        = (DH)    Field Separator
        0   1   1   1    0        > (EH)    Control
        1   1   1   1    1        ? (FH)    End Sentinel


           ***** 16 Character 5-bit Set *****
                10 Numeric Data Characters
                3 Framing/Field Characters
                3 Control Characters


The magstripe begins with a string of Zero bit-cells to permit the self-
clocking feature of biphase to "sync" and begin decoding.  A "Start Sentinel"
character then tells the reformatting process where to start grouping the
decoded bitstream into groups of 5 bits each.  At the end of the data, an "End
Sentinel" is encountered, which is followed by an "Longitudinal Redundancy
Check (LRC) character.  The LRC is a parity check for the sums of all b1, b2,
b3, and b4 data bits of all preceding characters.  The LRC character will catch
the remote error that could occur if an individual character had two
compensating errors in its bit pattern (which would fool the 5th-bit parity
check).

The START SENTINEL, END SENTINEL, and LRC are collectively called "Framing
Characters", and are discarded at the end of the reformatting process.
[/size]

For my project I am reading from Track #2:

 [size=8pt]  *** Track 2 Layout: ***

           | SS |  PAN  | FS |  Additional Data  | ES | LRC |

 SS=Start Sentinel ";"
 PAN=Primary Acct. # (19 digits max)
 FS=Field Separator "="
 Additional Data=Expiration Date, offset, encrypted PIN, etc.
 ES=End Sentinel "?"
 LRC=Longitudinal Redundancy Check 
[/size]
/*
 * Magnetic Stripe Reader
 * Original Code by Stephan King http://www.kingsdesign.com
 * Minor Tweaks made to fit on ATMEGA168
 * Reads a magnetic stripe.
 *
 */
 
int cld1Pin = 5;            // Card status pin
int rdtPin = 2;             // Data pin
volatile int reading = 0;            // Reading status
volatile int buffer[255];   // Buffer for data
volatile int i = 0;         // Buffer counter
volatile int bit = 0;       // global bit
volatile char cardData[40];          // holds card info
volatile int charCount = 0;  
 
void setup() {
  Serial.begin(9600); 
  
  // The interrupts are key to reliable
  // reading of the clock and data feed
  attachInterrupt(0, changeBit, CHANGE);
  attachInterrupt(1, writeBit, FALLING);
  
}

 
// Flips the global bit
void changeBit(){
  if (bit == 0) {
    bit = 1;
  } else {
    bit = 0;
  }
}
 
// Writes the bit to the buffer
void writeBit(){
  buffer[i] = bit;
  i++;
}



void loop(){
  // Active when card present
  while(digitalRead(cld1Pin) == LOW){
    reading = 1;
  }     
 
  // Active when read is complete
  // Reset the buffer
  if(reading == 1) {      
    sendData();
  }
  
}

// prints the buffer
void sendData(){
  decode();
  reset();
}

void reset() {
  reading = 0;
  i = 0;
  charCount = 0;
}

int getStartSentinal(){
  int sentinal = 0;
 
  for (int j = 0; j < i - 5; j++) {
    if (buffer[j] == 1 && buffer[j + 1] == 1 && 
       buffer[j + 2] == 0 && buffer[j + 3] == 1 && 
       buffer[j + 4] == 0) {
 
      sentinal = j;
      break;
    }
  }
  return sentinal;
}

void decode() {
  int sentinal = getStartSentinal();
  int j;
  int i = 0;
  int k = 0;
  int thisByte[5];
 
  for (j = sentinal; j < 255 - sentinal; j = j + 1) {
    thisByte[i] = buffer[j];
    i++;
    if (i % 5 == 0) {
      i = 0;
      if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0) {
        break;
      }
      printMyByte(thisByte);
    }
  }
 
  Serial.print("Stripe_Data:");
  for (k = 0; k < charCount; k = k + 1) {
    Serial.print(cardData[k]);
  }
  Serial.println("");
 
}

void printMyByte(int thisByte[]) {
    cardData[charCount] = decodeByte(thisByte);
    charCount++;
}

char decodeByte(int thisByte[]) {
    if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 1){
      return '0';
    }
    if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0){
      return '1';
    }
 
    if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 0){
      return '2';
    }
 
    if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 0 & thisByte[4] == 1){
      return '3';
    }
 
    if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 0){
      return '4';
    }
 
    if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 1){
      return '5';
    }
 
    if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 1){
      return '6';
    }
 
    if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 0 & thisByte[4] == 0){
      return '7';
    }
 
    if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 0){
      return '8';
    }
 
    if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 1){
      return '9';
    }
 
    if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 1){
      return ':';
    }
 
    if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 0 & thisByte[3] == 1 & thisByte[4] == 0){
      return ';';
    }
 
    if (thisByte[0] == 0 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 1){
      return '<';
    }
 
    if (thisByte[0] == 1 & thisByte[1] == 0 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 0){
      return '=';
    }
 
    if (thisByte[0] == 0 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 0){
      return '>';
    }
 
    if (thisByte[0] == 1 & thisByte[1] == 1 & thisByte[2] == 1 & thisByte[3] == 1 & thisByte[4] == 1){
      return '?';
    }
}

This could potentially have nothing todo with what you’re talking about though lol :blush: