Hack from SPI display to Arduino

Well, a little progress, but I'm stuck again. To bone up on SPI I used Nick's article, thank you. I set up an Arduino 2009 as a slave and a Mega1280 as the Master. I put the logic analyzer pins on the slave and got them communicating the best using the second pair of Nick's programs. I got the Master to simulate the signals and pictures that are coming from the Time and Attendance LCD and hacked enough of a program on the Slave so I could identify the graph/picture for at least the digits 0-9. I got the interrupt routine short enough to work at 4 megaHz and since the T&A system is at 2.4 megaHz I thought it was time to move on.

Then I hooked the Slave and logic analyzer to the T&A LCD and ran into what turned out to be a big problem. Both the clock and the Slave Select voltages on the T&A system are inverted (voltage high vs. low) from the way SPI is set up in the SPI library. There is a clock polarity setting in SPI.setDataMode() which I think solves the former, but I can't find any way to change the Slave Select polarity.

Any ideas in software or settings?

I'm not much with electronic hardware but I'm sure there is some easy way to invert either of these signals with a transistor or two. Any reference to how to do that?

PS. My son is visiting his in-laws in Florida. They are originally from somewhere just north of Melbourne, I think Seymour?

RickRantilla:
There is a clock polarity setting in SPI.setDataMode() which I think solves the former, but I can't find any way to change the Slave Select polarity.

Any ideas in software or settings?

Well, slave select is pretty-much manual, so you just do a digitalWrite HIGH rather than LOW, and you solved that one.

PS. My son is visiting his in-laws in Florida. They are originally from somewhere just north of Melbourne, I think Seymour?

About two hours drive north, but yes, I know Seymour.

Thanks, Nick. I got the clock mode fixed and found another line from the motherboard with a byte frame signal that I'm using as a SlaveSelect signal with the correct polarity. This causes a minor problem in that I have to also look at and discard all the command bytes, but no big deal.

Thanks, Graynomad. Actually there are about 10 bits per byte so I have about 4 microsec to do something with the SPDR byte before it gets overwritten. The time and attendance to LCD clock is running at about 2.4 megaHz (400 nanosec). 4 microsec is about 64 Arduino assembly instructions.

I simulated the LCD with another Arduino at 4 megaHz SPI and got everything hobbling along. However, when I wire up to the Time and Attendance LCD I'm losing about 5% of the bytes in the Interrupt Service Routine. I'm getting clear bytes on the logic analyzer. I put debug timers in the ISR and with Arduino microsec() resolution I get mostly 4 microsec but occasionally 8 microsec for the loop, which is marginal. I don't know what causes the longer ISR or whether that's when I lose a byte. Since each valid userID has 8 digits in it I'd have to have the patients rescan about 40% of the time, which I think is unacceptable. Even worse might be the occasional false positive.

I've written the ISR with a switch case conditional to capture any of the 10 numerals it might be looking at.

Here is the code. Any idea how to speed it up? Or do I need to try a faster processor like the Maple?

// SPI interrupt routine
ISR (SPI_STC_vect)
{
//unsigned int time = 0; time = micros(); // debug
cp = c; // store previous digit needed to distinguish a 1 or 8
c = SPDR; // SPDR is the byte as SPI finds it

switch (c) // The T&A LCD displays the userID as an 8 digit graphic including 4 leading zeroes
// each graphic bitmap has 16 bytes per digit displayed
// Among the displayed digits, I found each digit has a unique byte (or byte pair) somewhere in it
// So whenever I find this unique byte in a field of digits, I've identified a digit
// I might also get these "unique bytes" in a field of commands or letters, but never with 4 leading zeroes
// Whenever I find a likely digit I store it in the buffer array digit[] to extract the userID later
{
case 71:
digit [j++] = 0; // Rick e
break;
case 96:
if (cp == 192) {digit [j++] = 1; }// Rick n
break;
case 67:
digit [j++] = 2; // Rick d
break;
case 30:
digit [j++] = 3; // Rick d
break;
case 19:
digit [j++] = 4; // Rick d
break;
case 33:
digit [j++] = 5; // Rick e
break;
case 48:
digit [j++] = 6; // Rick d
break;
case 1:
digit [j++] = 7; // Rick d
break;
case 62:
if (cp == 127) {digit [j++] = 8; }// Rick n
break;
case 35:
digit [j++] = 9; // Rick d
break;
default:
//time = micros() - time; Serial.println (time, DEC); delay(100); // debug
break;
} // end of switch
} // end of interrupt service routine (ISR) SPI_STC_vect

Thanks in advance, Rick.

How have you defined all those variables?

Possibly the 5% is because of the timer interrupt kicking in. You could probably turn that off, however then micros() and millis() wouldn't work.

What would probably be faster than a switch would be a table lookup. That is, directly index into a table to find the correlation between the code you receive and the translated code. You would still have a problem with 1 and 8 though.

The other thing would be to stuff it into a buffer (eg. buf [pos++] = SPDR; ). Then worry about interpreting the buffer when you have time on your side.

Any idea how to speed it up?

If you can afford 72 bytes for a sparse array lookup table and use a pointer instead of indexing into an array it should be faster

// SPI interrupt routine

volatile BYTE vals [72] = {x,x,x,x,x,}; // put values in here, 0 if not used
volatile BYTE * j = digit;

ISR (SPI_STC_vect)
{
  cp = c; //  store previous digit needed to distinguish a 1 or 8
  c = SPDR; // SPDR is the byte as SPI finds it
     
	if (c == 96 && cp == 192) {
		*j = 1; 
	} else if (c == 62 && cp == 127) 
		*j = 8; 
	} else {
		*j = vals[c];
	}
	j++;
}

Not tested (or even compiled) and I'm sure the first two ifs could be removed with some thought and more knowledge of the data, but that has to be faster.

Also you are losing a hell of a lot of time just servicing the ISR, polling will be faster.


Rob

Well he says it works 95% of the time, so a bit of speed up in array lookups might help. And if he is using int rather than byte you could save a bit there.

However I agree with Rob, I reckon it takes around 3.5 uS to enter an ISR (we looked into it before, there are about 22 54 clock cycles involved) so if time is critical, polling would probably be faster. Of course you have to stop polling and do something with the results at some stage. If you do poll, you could turn off all interrupts (thus stopping the timer interrupts) which should make it more reliable.

I worked it out here:

54 cycles to enter the interrupt routine (3.38 uS). And roughly the same amount to leave it.

naked_Isr can cut the entry time quite a bit.

Good point about the polling though.

I've use a naked ISR before but in an app that had to save every reg in a known order so I used ASM pre/postambles.

How would naked work if you just had C code in the ISR? You couldn't use any variables or do anything except IO I would think.


Rob

You could be right there.

Anyway I await a response to reply #8. :slight_smile:

Well, thanks, guys, for sticking with me. I thought this was going to be a one or two evening project, but... now my wife says I'm obsessed. And in a sense I guess I am. If you lose interest I'll get by somehow. I ordered a Maple supposedly Arduino compatible that uses a different processor that runs a 5 times the speed. That should give me time to complete the analysis within the ISR.

I tried just stuffing the data into a buffer like you suggested in #8 and couldn't get that to work. I couldn't find a way to stop the buffer from overflowing when data comes in too rapidly. I have to analyze something before I tell it to stop.

Data bursts are at about 250,000 bytes per second. "Normal mode" is a couple of data bursts per second to display a clock and whatnot. These bursts range from 66 bytes each to occasionally 1062 bytes. (I might get a few dozen bytes inside these "normal" bursts that contain the "unique bytes" of the 10 numerals)

Other data: Of the 256 byte values only about 100 appear to ever get used as digits, letters or commands. (I looked at a string of 40,000 bytes as a representative sample. As long as I don't crash the system, all this normal mode data is junk. I'll don't think I'll ever get a false positive from it.)

A good finger swipe results in 4 bursts within two seconds, followed by 2 seconds of silence. They are 1024 bytes, 704 bytes, 1502 bytes (or 1216 bytes depending on how it clears the screen) and 66 bytes long. The first two bursts tell the user to remove their finger, the third provides information including the userID. The last 176 bytes of the 1502 byte burst are four zeroes and four digit drawings of the User ID.

(Each digit drawing is a 3 byte location command, 8 bytes of upper bitmap data, 3 more bytes location and 8 more bytes of lower bitmap data.)

The last 66 byte burst contains a colon and could be the cue to process the raw_digit string in the buffer. I poked around some more and just before the User ID string is the word "OK!" and the "K" contains a useful unique byte that can be used as a start collecting data signal.

Here is the code I'm using now:

All counters are volatile int and all data are volatile byte data types.

void setup (void)
{
Serial.begin (115200);
Serial.println (sketch); // Rick - always start by identifying what's actually in the Arduino

// have to send on master in, slave out
pinMode(MISO, OUTPUT);

// turn on SPI in slave mode
SPCR |= _BV(SPE);

// turn on interrupts
SPCR |= _BV(SPIE);

SPI.setDataMode(SPI_MODE2); // Rick - because of inverted clock in T&A - not used in testing between Arduinos

} // end of setup

// SPI interrupt routine
ISR (SPI_STC_vect)
{
if (SPDR==13) {start = 1;} // Rick - unique part of "OK" before User ID
if (SPDR == 198) {process = 1;} // Rick - unique inverse colon after UserID
if ((start==1) && (r < rmax)) {raw_digit[r++] = SPDR; } // Rick - only collect when within range
// Rick - I still need rmax in case I miss a 198 - may be overkill

} // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
{
if ((r >= rmax) || (process == 1))// Rick - ISR got an inverse colon - rmax if I missed it
{
// Rick - caution: in a millisec ISR will start overwriting raw_digit[0]
rtemp = r;
r=0; // Rick - let interrupt go back to filling raw_digit
process = 0; start = 0; // Rick - wait for new start/stop from ISR
for (int i=0; i <= rtemp; i++){ work_digit = raw_digit*; } // Rick - get these moved in a hurry*

* for (m=0; m <=rtemp; m++) { getDigit(); } // Rick - m is global*
* // Rick - this for loop and getDigit returns with an array good_digit[ ] with g valid digits in it
get_user_id(); // Rick - convert all the good_digits to any valid out_digit (or userID)
_
g=0;_
} // end if process NOTE: get_user_id function now has a couple of seconds while ISR refills raw_digit*

} // end of loop
My test userIDs are 00000001, 00000002 and 00001945. A recent run of results was 00000002 00000200 00002520
00000100 00002010 00000220 00000210 00000100 00000001 00000001 00000021. I rarely get a successful output on a 00001945, I think because at least one of the zeroes gets lost. On the LCD you can see typically "OK! ID 00000002 1:1" When I put debug timers and counters in there I see even worse gaps, but then the main loop is doing Serial.print and timers, etc. which might be screwing it up. Again the data on the logic analyzer is good. Somehow the ISR is simply missing one or more of the zeros (and likely other bytes as well).
Until the Maple comes in, I'm going to study table lookups. Unless you have another suggestion. By the way, what is naked_ISR? Is that a function someone wrote?
Thanks.

See how half your code is in italics? If you put it into [ code ] tags it won't do that. Select the code and hit the "#" button above the input box.

By the way, what is naked_ISR? Is that a function someone wrote?

it's packaged with the avr gcc compiler i believe. It lets you pick and choose what regs to save. In the one place I used it it cut the isr entry time from 3.5us to 1 and a bit. If you bound your "r" variable to a register i think you would only need to save and restore the status register. I actually learned about naked_isr and binding to registerd from a post here (maybe westfw's) but I can't find it any more..

The code I see in your post uses the ISR to stuff a buffer and the loop to analyze it - right? Where is the overrun or dropped data happening do you think? Does it just not make it into the buffer?

The loop code looks iffy to me. Let's say you missed a stop and you're over rmax: i think the isr will start overwriting the data that you're analyzing. You need some rock-solid way of keeping out of your own way:
e.g.in the loop, disable interrupts, copy all the data, reset the pointer and enable interrupts. If you miss a second swipe that starts that soon, too bad but at least your data is solid.

rtemp = r; 
    r=0; // Rick - let interrupt go back to filling raw_digit   
    process = 0; start = 0; // Rick - wait for new start/stop from ISR  
    for (int i=0; i <= rtemp; i++){ work_digit = raw_digit;  }  // Rick - get these moved in a hurry
    
    for (m=0; m <=rtemp; m++) { getDigit(); } // Rick - m is global
      // Rick - this for loop and getDigit returns with an array good_digit[ ] with g valid digits in it       

    get_user_id(); // Rick - convert all the good_digits to any valid out_digit (or userID)
    g=0;

I'll be interested in the maple - have you looked at its interrupt response times?

Nick, losing a byte or two every 1000 isn't a disaster, it will just require the patients to reswipe 8 or 16 times out of a 1000. The only time this should happen is when I hit rmax, currently set at 1229. I figure that when I hit rmax, after two moves r=0 so the ISR can collect data again, and two more moves plus setting up a "for loop" then moving the first few bytes out of raw_data and I'm safe again. Is my reasoning correct? But in the real world, using the 7 zeroes in a row as a good example I'm losing one or more of those zeroes at least half the time, not 8 or 16 in 1000. (That's why I set rmax to 1229, so it wasn't likely to keep hitting the same seven zeroes.)

I'm now playing with a 256 byte lookup table for each possible SPDR bytes. (I tried a boolean table but I need more than 2 responses because of the 1 and 8 not having a unique byte.) The best I can do in the ISR still requires 3 "if" statements, one to decide if the byte is "potentially unique", a second to handle the 1 and 8 situation (which became 1,3 and 8 after I discovered a bug) and a third to distinguish a legitimate zero from an invalid/non-digit byte. Plus moving a few bytes around and I'm back where I started. It does have a lot of potential to eliminate downstream analysis, but it won't matter if I can't capture the raw data.

I mistakenly used the insert quote instead of insert code and all my [] bracket things disappeared. Rather than deal with it I just copied the code to get the mail out.

I think the only way to find out about the Maple is to try it when it arrives.

I'll look around for the naked_ISR references. Saving 1 or 2 microseconds might be enough.

I seriously think you need a rock solid protocol between the ISR and the loop.

I found the ISR_NAKED ref that I used myself: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1261537186
In my case I only needed a single variable and was able to copy westfw's code closely. You'd have to think hard about your flag variables but this would easily let you copy to the buffer much more quickly. Maybe you define one more register variable "isrstate" and use that to subsume start, process, and some sort of blocker flag from the loop to the isr.

Bill, thanks for the thought, but I don't think I'm that far down the road yet.

I just looked at my bit-byte to numeric digit map again and came up with a good way to both streamline the ISR using an improved lookup table and reduce the downstream analysis. The ISR only has a single if... move... increment statement, (and 9 out of 10 times the if is not taken) so that ought to be quick enough. The downstream processing just resets the ISR index and immediately starts moving bytes to a safe place.

Here is the entire code:

// Written by Nick Gammon
// April 2011

char sketch[] = "Nick_Gammon_Slave2_dec31x";  // Revisions by Rick Rantilla

// rev x - new raw2work chart - avoids copying strings of bytes for valid digits. This may help ISR timing        
// rev w - using 256 member raw2work lookup table
// rev s - master counter "count" added for debugging
// rev p - just capture byte is ISR - everything else outside
// rev n - Added timing counters for debug - Why am I losing about 5% of the valid digits in the ISR?
//         Since a valid userID has 8 digits, I'm losing or distorting 40% of them!!!
//         Per timing ISR takes only 4 or 8 microsec, however, LCD gets 1 byte every 4 microsec.
// rev m - Ugh! - Serial.print takes hundreds of microseconds per character even at 115200
// rev k - Mega: now using wire 15 from T&A as SS (unfortunately this picks up commands as well)
//         Mega SPI pins: 53 SS, 51 MOSI (signal), 52 clock
//         Also SPI.setDataMode for inverted clock from the T&A LCD

#include "pins_arduino.h"
#include "SPI.h"  // rick k

volatile byte c = 0; // Rick m
volatile byte cp = 0; // Rick n - previous byte needed to distinguish some digits
volatile int g = 0; // Rick d - counter for storing bytes into good_digit array to examine at leisure
volatile int r = 0; // Rick p - counter for raw digits
volatile int rmax = 50; // Rick r
volatile int i = 0; // Rick p
volatile int j = 0; // Rick p
volatile int k = 0; // Rick p
volatile int p = 0; // Rick x - I've made all counters unique and global
volatile int count = 0; //  Rick s master counter

byte raw_digit [500]; // Rick p
byte work_digit[500]= "a"; // Rick n
byte good_digit [400];  // Rick d
byte out_digit[100]= "a"; // Rick n

volatile boolean raw2work[] = {0,1,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,1,0,0,0,0,  0,0,0,0,0,0,0,0,   // 0-31  Rick w
                               0,1,0,1,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,1,0,1,0,   // 32-63
                               0,0,0,1,0,0,0,1,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,   // 64-95
                               1,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,1,1,   // 96-127
                               0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,   // 128-159
                               0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,   // 160-191
                               1,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,   // 192-223
                               0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0  };// 224-255
                // Rick x - only bytes in this table pass thru to the next step of decoding


void setup (void)
{ 
  Serial.begin (115200);
  Serial.println (sketch); // Rick c - always start by identifying what's actually in the Arduino

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);   

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);
  
   SPI.setDataMode(SPI_MODE2);  // Rick k - because of inverted clock in T&A - not used in testing between Arduinos
}  // end of setup


ISR (SPI_STC_vect)  // SPI interrupt routine
{
  //count++;
  if (raw2work[SPDR]) {raw_digit[r++] = SPDR; // Rick x - if it is in the table, store it 
    // Rick w - it is a potential unique digit and is worth storing for further analysis
    //Serial.println (raw_digit[r-1]); //Serial.print (" "); Serial.println (r,DEC);// debug
  }
}  // end of interrupt service routine (ISR) SPI_STC_vect


void loop (void)
{
  if (r >= rmax) // Rick w - just be patient, don't get too fancy
  { // Rick m - caution: in a millisec ISR will start overwriting raw_digit[0]
    r=0; // Rick n - let interrupt go back to filling raw_digit   
    
    for (i=0; i <= rmax; i++){ work_digit[i] = raw_digit[i];  }  // Rick n - get these moved in a hurry, most of these are already good_digit
    //for (int i=0; i <= rmax; i++){Serial.println(raw_digit[i]); } // Rick v - debug
    
    for (j=0; j <=rmax; j++) { getDigit(); } // Rick x - get only valid digits, requires valid predecessors for 1, 3 and 8
      // Rick p - this for loop ends with an array good_digit[ ] with g valid digits  

    for (k=0; k < g; k++){ // Rick n - process all g good_digits
      // A valid userID is 4 zeroes followed by 4 digits.  I've never seen a string of 4 zeroes in the command fields
      if ( (good_digit[k] == 0) && (good_digit[k+1]==0) && (good_digit[k+2]==0) && (good_digit[k+3]==0) )
        { // got a valid userID - print the 4 least significant digits
          for (p=0; p <= 7; p++) { // Rick n - again i is a local variable
            out_digit[p] = good_digit[k+p];  // Rick n - userID is the 4 digits after the four zeroes
            Serial.print (out_digit[p], DEC);  // debug
          } // end for p
          Serial.println(" ");//Serial.println(count);  // debug
          
          // Rick x - here I will insert my cabinet door operation application code using out_digit
          
          k = k+7; // k++ already incremented once - skip past the 8 digits of the UserID
        } // end if good_digit - what happens if at the end of work_digit??? - adds trailing zeroes - I need to clean this up later
    } // end for k
    g=0; // set up for next batch
  } // end if r - most of the time loop does nothing but check r
} // end loop


void getDigit()  // Rick p
{
  cp = c; // Rick n - store previous digit needed to distinguish a 1, 3 or 8
  c = work_digit[j]; // still the raw byte as SPI found it, but only those in raw2work lookup table
     
  switch (c)  // Rick n - each graphic bitmap has 16 bytes per digit displayed (plus 6 command digits)
              // Among the digits, each digit has a unique byte (or byte pair)
              //   96, 192, 62, 126 and 127 may also appear in other digits, but unique pairings finalize the selection
              // So whenever I find this unique byte (pair) in a field of digits, I've got a good digit
              //   regardless of what else is in that digit
              // I might also get what appears to be a digit in a field of commands or letters or the clock, 
              //   but I will later throw them out unless they are immediately following 4 zeroes in a row
              // Increment g only if a valid digit

  {
  case 71:
    good_digit [g++] = 0;  // Rick e
    break;
  case 96:  // original 127 remapped to 11 if cp was 0
    if (cp == 192) {good_digit [g++] = 1; }// Rick t
    break;  
  case 67:
    good_digit [g++] = 2;  // Rick d
    break;
  case 62:  // original 32 remapped to 5 if cp was 0
    if (cp == 126) {good_digit [g++] = 3; } // Rick t
    if (cp == 127) {good_digit [g++] = 8; }// Rick t
    // Note: there is a bug where sequence "48" in a valid user ID would register as "488" 
    //       since a "4" ends in "127" and the "8" starts with "62" 
    //       although these bytes are not part of their "unique" byte pairs
    break;  
  case 19:
    good_digit [g++] = 4;  // Rick d
    break;
  case 33:
    good_digit [g++] = 5;  // Rick e
    break;
  case 60:
    good_digit [g++] = 6;  // Rick d
    break;
  case 1:
    good_digit [g++] = 7;  // Rick d
    break; 
  case 35:
    good_digit [g++] = 9;  // Rick d
    break; 
  default:
  //time = micros() - time; Serial.println (time, DEC); delay(100); // debug
    break;
  } // end of switch
}  // end of getDigit

Here are the results of what should have been 1,1,1,2,2,2,1945,1945,1945,3,3,3 (all with leading 0s):

00000001
00000001
00000008 (could have missed a 0)
00000002
00002114 (could have missed 3 0s)
00000217 (could have missed 2 0s)
(nothing - could have missed a 0)
(nothing - could have missed a 0)
00001945
00000351 (could have missed 2 0s
00000003
00004444 (could have missed 3 0s and the 3)

In this run rmax was set at 50 bytes, so I should have expected "end of buffer" effects 8/50 or about 2/12, not 7/12. If I set rmax at a big number (like 1000) I'd have to wait minutes for a response.

I'm going to see if I can find another Arduino laying around (maybe a newer Uno) before starting on a new venture to clean up the buffer ends or study naked_ISR. What I could do is set my buffer raw_digits to 1000, but only analyze 50 byte segments into work_digit, etc. Hmmm....

Thanks

Some suggestions:

  • Get rid of the volatile keyword except for anything shared between the ISR and the main loop. That actually slows things down because the compiler is forced to reload registers. Basically raw_digit and r. (raw2work doesn't change and clearly isn't volatile).

  • Make rmax a const, so the compiler can optimize it into a literal load.

  • Change everything you can from int to byte. After all it is an 8-bit machine. Specifying int requires more cycles than byte.

  • Take the "if" out of the ISR:

if (raw2work[SPDR]) {
    raw_digit[r++] = SPDR; // Rick x - if it is in the table, store it

(Do tests later.)

  • I would use a circular buffer - that handles bursts quite well. The ISR just stuffs things into the buffer (and if you make it 256 bytes a "byte" counter will wrap around without you having to tell it to).

  • Then in "loop" use a "remove" pointer to pull stuff out of the buffer (up to the insert point) - that lets you decode more slowly.

There is an example of inserting/removing from a circular buffer in the code for a debugging slave here:

Nick, great! Thanks! I'm happy!

I got rid of "volatile" and changed everything I could to single bytes. I figured out the automatic circular buffers which are really neat. I now get a quick response and miss something occasionally, but not horrendously. I just ran 40 trials and missed a zero byte on 3 trials and garbled up enough to get no response on 2 others. That's 12% which is no worse than the finger sensor reject rate itself. The 40 trials made the program go through all the 256 byte buffers a couple of times, and I don't see any issues there and don't think they are the cause of the occasional missed bytes.

(By the way, the fingerprint reader is on my left side and I've been ackwardly reaching around with my right hand to get test readings for several days now. It just occurred to me to "register" the middle finger of my left hand and now I get satisfaction from giving the reader my left finger several times in a row.)

I'm reluctant to remove the "if" statement from the ISR because it eliminates over 90% of the junk data that I don't have to process at all downstream. Also rather than checking and stop reading if "inpoint" ever catches "outpoint" I'll just live with what happens... that is possibly what happened in the 2 garbled responses.

Now that I've got some decent data I've noticed that when I drop a zero, if UserID was say 00000067 it becomes something like 00000671, which might be a false positive. If I can't overcome this I'll put some restrictions on valid user IDs to compensate.

Other than that I think I'm good to go. Good thing, because I just got to revision z and hate to go to capital letters. Also the Maple just arrived in the mail. Don't know if I'll open it this month.

Thanks again, everyone. I couldn't have done it without you. Let me know if there is anything I can do for you all.

PS. Happy New Year!

Here is the code if anyone wants to use it as a (poor) example.

// Written by Nick Gammon
// April 2011

char sketch[] = "Nick_Gammon_Slave2_dec31z";  // Revisions by Rick Rantilla

// rev z - automatic 256 byte buffers
//         initialize good_digit with something besides zeroes
// rev y - Nick's advice on bytes and volatile
// rev x - new raw2work chart - avoids copying strings of bytes for valid digits. This may help ISR timing        
// rev w - using 256 member raw2work lookup table
// rev s - master counter "count" added for debugging
// rev p - just capture byte is ISR - everything else outside
// rev n - Added timing counters for debug - Why am I losing about 5% of the valid digits in the ISR?
//         Since a valid userID has 8 digits, I'm losing or distorting 40% of them!!!
//         Per timing ISR takes only 4 or 8 microsec, however, LCD gets 1 byte every 4 microsec.
// rev m - Ugh! - Serial.print takes hundreds of microseconds per character even at 115200
// rev k - Mega: now using wire 15 from T&A as SS (unfortunately this picks up commands as well)
//         Mega SPI pins: 53 SS, 51 MOSI (signal), 52 clock
//         Also SPI.setDataMode for inverted clock from the T&A LCD
//
//  This program is a hack of a Time and Attendance system with a fingerprint reader.  My application needs
//      the userID in "real time" but the system is designed to only output in batch.  
//      The system does display the userID immediately after each successful read on an attached LCD, 
//       but only in bit mapped graphics form.  So I've figured out what bits & bytes represent which numbers
//       and am tapping the motherboard to LCD lines using an Arduino as an SPI slave.

#include "pins_arduino.h"
#include "SPI.h"  // rick k

byte c = 0; // Rick m
byte cp = 0; // Rick n - previous byte needed to distinguish some digits
byte g = 0; // Rick d - counter for storing bytes into good_digit array to examine at leisure
//volatile byte r = 0; // Rick p - counter for raw digits

volatile byte inpoint = 0;  // Rick y
volatile byte outpoint = 0;  // Rick y

//byte rmax = 255; // Rick r - no longer needed, wrap at end of byte
byte i = 0; // Rick p
byte j = 0; // Rick p
byte k = 0; // Rick p
byte p = 0; // Rick x - I've made all counters unique and global
volatile int count = 0; //  Rick s master counter

byte raw_digit [255]; // Rick p
byte work_digit[255]= "a"; // Rick n
byte good_digit [255];  // Rick d
byte out_digit[100]= "a"; // Rick n

volatile boolean raw2work[] = {0,1,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,1,0,0,0,0,  0,0,0,0,0,0,0,0,   // 0-31  Rick w
                               0,1,0,1,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,1,0,1,0,   // 32-63
                               0,0,0,1,0,0,0,1,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,   // 64-95
                               1,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,1,1,   // 96-127
                               0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,   // 128-159
                               0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,   // 160-191
                               1,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,   // 192-223
                               0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0  };// 224-255
                // Rick x - only bytes in this table pass thru to the next step of decoding


void setup (void)
{ 
  Serial.begin (115200);
  Serial.println (sketch); // Rick c - always start by identifying what's actually in the Arduino

  for (i=0; i<=254; i++) {good_digit[i] = 9;}  // Rick z - initialize so stray zeroes don't trigger output
  
  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);   

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);
  
   SPI.setDataMode(SPI_MODE2);  // Rick k - because of inverted clock in T&A - not used in testing between Arduinos
}  // end of setup


ISR (SPI_STC_vect)  // SPI interrupt routine
{
  //count++;  // debug
  if (raw2work[SPDR]) {raw_digit[inpoint++] = SPDR; // Rick x - store it 
    // Rick w - it is a potential unique digit and is worth storing for further analysis
    //Serial.println (raw_digit[r-1]); //Serial.print (" "); Serial.println (inpoint-1,DEC);// debug
  }
}  // end of interrupt service routine (ISR) SPI_STC_vect


void loop (void)
{
  if (outpoint != inpoint) // Rick w - just be patient, don't get too fancy - assume it is behind
  {           // Rick m - caution: in a millisec ISR will start overwriting raw_digit[0]    
    work_digit[outpoint] = raw_digit[outpoint++];    // Rick n - get these moved in a hurry, most of these are already good_digit
      
    getDigit();  // Rick x - converts only valid digits, requires valid predecessors for 1, 3 and 8
      // Rick p - this function returns with the next good_digit[ ] at g  
      // Rick y - run through the rest of the loop even if no new g?
      // Rick y - is this digit the end of an 8 digit userID?
      // A valid userID is 4 zeroes followed by 4 digits.  I've never seen a string of 4 zeroes in the command fields
    if ( (good_digit[g-8] == 0) && (good_digit[g-7]==0) && (good_digit[g-6]==0) && (good_digit[g-5]==0) )
      { // got a valid userID - print the 4 least significant digits
          Serial.print("User ID = ");  // debug
          for (p=0; p <= 7; p++) { // Rick n 
            out_digit[p] = good_digit[g-8+p];  // Rick n - userID is the 4 digits after the four zeroes
            Serial.print (out_digit[p], DEC);  // debug
            good_digit[g-8+p] = 99; // Rick y - so we don't try to use these digits again
          } // end for p
          Serial.println(" ");//Serial.println(count);  // debug
          
          // Rick x - here I will insert my cabinet door operation application code using out_digit
          
      } // end if good_digit - what happens if at the end of work_digit??? - adds trailing zeroes - I need to clean this up later   
  } // end if outpoint - most of the time loop does nothing but check inpoint/outpoint
} // end loop


void getDigit()  // Rick p
{
  cp = c; // Rick n - store previous digit needed to distinguish a 1, 3 or 8
  c = work_digit[outpoint-1]; // still the raw byte as SPI found it, but only those in raw2work lookup table
     
  switch (c)  // Rick n - each graphic bitmap has 16 bytes per digit displayed (plus 6 command digits)
              // Among the digits, each digit has a unique byte (or byte pair)
              //   96, 192, 62, 126 and 127 may also appear in other digits, but unique pairings finalize the selection
              // So whenever I find this unique byte (pair) in a field of digits, I've got a good digit
              //   regardless of what else is in that digit
              // I might also get what appears to be a digit in a field of commands or letters or the clock, 
              //   but I will later throw them out unless they are immediately following 4 zeroes in a row
              // Increment g only if a valid digit found

  {
  case 71:
    good_digit [g++] = 0;  // Rick e
    break;
  case 96:  // original 127 remapped to 11 if cp was 0
    if (cp == 192) {good_digit [g++] = 1; }// Rick t
    break;  
  case 67:
    good_digit [g++] = 2;  // Rick d
    break;
  case 62:  // original 32 remapped to 5 if cp was 0
    if (cp == 126) {good_digit [g++] = 3; } // Rick t
    if (cp == 127) {good_digit [g++] = 8; }// Rick t
    // Note: there is a bug where sequence "48" in a valid user ID would register as "488" 
    //       since a "4" ends in "127" and the "8" starts with "62" 
    //       although these bytes are not part of their "unique" byte pairs
    break;  
  case 19:
    good_digit [g++] = 4;  // Rick d
    break;
  case 33:
    good_digit [g++] = 5;  // Rick e
    break;
  case 60:
    good_digit [g++] = 6;  // Rick d
    break;
  case 1:
    good_digit [g++] = 7;  // Rick d
    break; 
  case 35:
    good_digit [g++] = 9;  // Rick d
    break; 
  default:
  //time = micros() - time; Serial.println (time, DEC); delay(100); // debug
    break;
  } // end of switch
}  // end of getDigit