Arduino Parts Counter

I have a project building a small parts counter. As it stands now I need the motor to reverse when it senses a jam. Coding this has me stumped. I have tried numerous times, but no luck. Your help will be much appreciated.

Project hardware:
Arduino MEGA 2560
Rotary Encoder KY-040
LMioEtool DC motor, reversible, 12v, 10 rpm
Chanzon 12v, 3A, 36W AC-Dc switching wall wart
L298N Motor drive controller, Dual H-bridge
5v Solid State Relay Module
4 x 4 Membrane Keypad.
16 x 2 Liquid Crystal Display.
Adafruit IR Break Beam Sensor (ADA2168))
Potentiometer (for LCD).
2 push buttons (one for external reset, one for cycle start.)
1 10k resistor (cycle start pushbutton)
1 LED (blue) mimics the onboard pin 13 LED.
1 220 ohm resistor (for blue LED)
1 capacitor 0.1uF-- (across LCD pins 1&2)

Project Goal:
LCD prompts user for input.
User enters a desired package quantity. (This quantity is stored until the Arduino Mega is reset.)
Motor turns on.
Parts pass thru the IR beam and are counted.
Motor stops when the desired quantity is reached.
If the motor stalls then reverse the motor for 1 second to clear the jam
Press “Cycle Start” button to begin again. The ‘cycle start’ button will begin the cycle over.
(I have an external ‘reset’ button that resets the program (entire board) when a new part is run or quantity changes.
It is connected to the Mega’s reset pin)

How it actually works:
LCD prompts for input.
The desired quantity is printed on LCD and stored in ‘value’.
Motor turns on.
Need to code the motor reverse feature for the motor.
Beam Break sensor operates somewhat correctly sensing parts and counting them. (It miscounts though. This is a mechanical issue.)
The motor turns off on the correct beam break.
The counter resets to zero.
The entered quantity remains until the “Reset” button is pressed.
Press the cycle start button. Green LED flashes
Count field on the LCD clears, the motor turns on

PROBLEMS:
Beam break sensor can operate out of cycle. It would be best if it could only read in cycle. The counter should not increment while the motor is not running. I haven’t figured this out yet.

#include <Keypad.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(22,23,24,25,26,27);//Mega pins RS,E,D4,D5,D6,D7
//KEYPAD***************
const byte ROWS=4;
const byte COLS=4;
char hexaKeys[ROWS][COLS]=//define the buttons of the keypad
{{'1','2','3','A'},
 {'4','5','6','B'},
 {'7','8','9','C'},
 {'0','0','0','D'} };//changed the * & # values to zero  
byte rowPins[ROWS]={35,34,33,32};
byte colPins[COLS]={31,30,29,};//disabled the letter keys by leaving out #28
Keypad customKeypad=Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
int ledPin=13;//on board LED (mega)and an external blue LED pin
int sensorPin=4;//break beam sensor is pin 4-blinks blue LED pin 13
int sensorState=0,lastState=0;//variable for reading the sensor status
int BBcounter=0;//holds the number of beam breaks
int KPvalue=0;//holds the key press quantity desired
int cyclePin=2;//cycle start pushbutton
int cycleState=0;// current cyclePin state
int lastCycleState=0;// previous cyclePin state
//H-bridge-12VDC motor
int enA=10;                         
int in1=12;                         
int in2=11;                         
// Rotary Encoder Inputs
int CLK=14;                        
int DT=15;                         
int ledCCW=9;//LED indicates the rotary disc is moving CCW 
int ledCW=8;//LED indicate the rotary disc is going CW 
int ENcounter=0;//encoder count
int currentStateCLK;//the current CLK pin state       
int previousStateCLK;//holds the previous CLK pin state  
String encdir ="";//holds the encoder's current direction
//ENCODER DEBOUNCE CODE
long lastDebounce=0;//Variables to debounce Rotary Encoder
int delayDebounce=0.01;//debounce delay time
int PreviousCLK;//previous CLK pin state 
int PreviousDT;//previous DT pin state
int displaycounter=0;//Store current encoder value
int oldENcounter=0;
int newENcounter=0; 
void setup(){
//H-bridge
pinMode(enA,OUTPUT);
pinMode(in1,OUTPUT);
pinMode(in2,OUTPUT);
//motor initial state=LOW
digitalWrite(in1,LOW);
digitalWrite(in2,LOW);
analogWrite(enA, 255);//enables motor  
pinMode(ledPin,OUTPUT);//initialize the onboard LED pin as an output:   
pinMode(sensorPin,INPUT_PULLUP);//initialize the sensor pin as an input: HIGH by default. Must use pullup or 10k resistor  
lcd.begin(16,2);//tells arduino which LCD is being used
pinMode(cyclePin,INPUT);//cyclePin is an input
//encoder 
pinMode (CLK,INPUT);//Set encoder pins as inputs 
pinMode (DT,INPUT);
pinMode (ledCW,OUTPUT);//Set directional LED pins as outputs
pinMode (ledCCW,OUTPUT);
Serial.begin (2000000);
previousStateCLK = digitalRead(CLK);// Read the initial state of inputCLK  
}
void loop(){ 
// Debounce encoder 
if ((millis()-lastDebounce)>delayDebounce)//If enough time has passed check the rotary encoder
{check_rotary();//Rotary Encoder check routine below    
PreviousCLK=digitalRead(CLK);
PreviousDT=digitalRead(DT);    
lastDebounce=millis();}//Set variable to current millis() timer
//KEYPAD - LCD
lcd.setCursor(0,0);                
lcd.print("Enter Qty");//Prompt the user for input  
char KPkey=customKeypad.getKey();   
if(KPkey){
KPvalue=(KPvalue*10)+KPkey-48;//store the value of the keypress until Reset is pressed.
lcd.setCursor(10,0);//set the cursor                 
lcd.print(KPvalue); }//Print the "Enter Qty" (value)
if(KPkey){
digitalWrite(in1,HIGH);//turns motor on CW
digitalWrite(in2,LOW);}
// ENCODER
currentStateCLK = digitalRead(CLK);//Read the current state of input CLK    
if (currentStateCLK != previousStateCLK){     
if(!currentStateCLK){//If the input DT state is different than the input CLK state then the encoder is rotating counterclockwise
 if (digitalRead(DT) != currentStateCLK){//I switched ++ to -- and -- to ++ in this section
ENcounter ++;//because the encoder runs CCW while the part disc runs CW
encdir ="CW";
digitalWrite(ledCW,LOW);// turns CW green LED on
digitalWrite(ledCCW,HIGH);
}else{// Encoder is rotating CCW (Rotary disc/motor is going CW, which is what I want to monitor)
ENcounter--;
encdir ="CCW";
digitalWrite(ledCW,HIGH);//turns CCW red LED on
digitalWrite(ledCCW,LOW);} 
//ATTEMPT TO CONTROL JAMMING OR STALLING
if(encdir!="CW"){
digitalWrite(in1,LOW);//turns motor on CCW
digitalWrite(in2,HIGH);
delay(1000);      
digitalWrite(in1,HIGH);//turns motor on CW
digitalWrite(in2,LOW);}        
Serial.print(encdir);
Serial.print(" ");
Serial.println(ENcounter);}  
previousStateCLK = currentStateCLK;//set previousStateCLK to the currentStateCLK
}
//SENSOR  
sensorState = digitalRead(sensorPin);//read the state of the sensor  
if (sensorState == LOW){//check if the sensor beam is broken, if it is, the sensorState is LOW
digitalWrite(ledPin, HIGH);}//turn LED on
else {digitalWrite(ledPin, LOW);}//turn LED off        
if(sensorState && !lastState){}//do nothing      
 if (!sensorState && lastState){
BBcounter++;      
lcd.setCursor(0,1);   
lcd.print("Count:");
lcd.print(BBcounter);}//prints the current beam breaks      
lastState=sensorState;     
if(BBcounter!=KPvalue);{}//do nothing if value and counter do not match
 if(BBcounter==KPvalue){//if counted parts (counter) == required parts (value)
digitalWrite(in1,LOW);//turn motor off 
digitalWrite(in2,LOW);
BBcounter=0;//reset both counters (beam break, encoder)
ENcounter=0;}
//CYCLE START 
cycleState=digitalRead(cyclePin);//read the cycleStart pin
if(cycleState!=lastCycleState){//compare cycleState to its previous state
 if(cycleState==HIGH){//if the state changes to HIGH turn motor on
digitalWrite(in1,HIGH);//motor runs clockwise
digitalWrite(in2,LOW);
lcd.setCursor(6,1);   
lcd.print("  ");}
else{}//if cycleState is LOW do nothing  
delay(100);}//delay to avoid bouncing
lastCycleState=cycleState;}//save the current cyclestate as the lastcyclestate, for next time through the loop
//Check if Rotary Encoder has moved
void check_rotary(){         
if((PreviousCLK == 0)&&(PreviousDT==1)){
 if((digitalRead(CLK)==1)&&(digitalRead(DT)==0)){
displaycounter++;}      
if((digitalRead(CLK)==1)&&(digitalRead(DT)==1)){
displaycounter--;}}     
if((PreviousCLK==1)&&(PreviousDT==0)){
 if((digitalRead(CLK)==0)&&(digitalRead(DT)==1)){
displaycounter++;}  
if((digitalRead(CLK)==0)&&(digitalRead(DT)==0)){
displaycounter--;}}      
if((PreviousCLK==1)&&(PreviousDT==1)){
 if((digitalRead(CLK)==0)&&(digitalRead(DT)==1)){ 
displaycounter++;}      
if((digitalRead(CLK)==0)&&(digitalRead(DT)==0)){
displaycounter--;}}      
if((PreviousCLK==0)&&(PreviousDT==0)){
 if((digitalRead(CLK)==1)&&(digitalRead(DT)==0)){ 
displaycounter++;}      
if((digitalRead(CLK)==1)&&(digitalRead(DT)==1)){ 
displaycounter--;}}            
}

I apologize. I meant to put this in Project Guidance.
Tim

Several points, in no particular order:

Unless part of your objective is to build a custom encoder handler, seriously consider using an encoder library. There's an effective one in the Arduino IDE -> file/examples folder. This can make the listing a lot cleaner and relieve you of most the housekeeping chores involved with an encoder.

Encoder = Rotary Encoder KY-040. In case you're not aware of it, this part is for things like manual menu selection applications. It is doubtful if it will last long being turned continuously by a motor.

The formatting needs work. Add some line spacing between functional sections - like before loop() and setup(), etc. Use ctrl to automatically indent the code. This alone is a great troubleshooting tool.

Why is the motor enabled with a PWM signal? That write in setup() is the only write to the motor output. Without a signal to stop it never will.

Timmcg55:
The counter should not increment while the motor is not running.

if (!sensorState && lastState && motorRunning) {

Doug,
Thanks for your reply.
Concerning the formatting. I did this because initially I was over the 9000 character limit. I eliminated spaces and all unnecessary text.
I did know about the encoder and suspected it will last a short time. Since this is a prototype for demonstration I used it just to prove the concept and get something working before I spend $$ on more hardware. I plan on using a motor with an encoder when I finalize this demonstration unit.

dougp:
Unless part of your objective is to build a custom encoder handler, seriously consider using an encoder library. There's an effective one in the Arduino IDE -> file/examples folder. This can make the listing a lot cleaner and relieve you of most the housekeeping chores involved with an encoder.

Could you possibly expand on this?

Thanks!
Tim

Navigate in the IDE to file/examples/Examples from custom libraries/encoder. Open the 'Basic' example. Set your pin numbers, upload the example sketch, bring up the serial monitor and twirl the encoder knob.

Doug, I tried as you suggested and the encoder works the same as in my sketch. I detect no difference and I'm curious as to what housekeeping chores it eliminates and how it makes the listing cleaner. consider me a newby since I've only a few months of coding.

All of this is missing the point I asked for help on, though I am very grateful for the opportunity to learn more, and your assistance.

In your original response you stated "Why is the motor enabled with a PWM signal? That write in setup() is the only write to the motor output. Without a signal to stop it never will." What about the digitalWrites in my encoder section. Do they not count?

When learning how to use the L298N H-bridge I found an online sample code. I tried it and it worked. Not knowing better I've stuck with it.

My main problem as I see it is polling when the encoder is not ++. I cannot poll the pins since they remain in the same state when the motor is stalled. Polling the encoder ++ signal has got me nowhere. Most of my attempts have resulted in the motor running CW and switching to CCW without any stalling of the motor.
As far as this suggestion of yours: "if (!sensorState && lastState && motorRunning) {". I tried this and ended up with the same result.

I am thankful for any help you might offer.

Tim

I missed the motor driver part! :frowning:

A couple of things:

int delayDebounce = 0.01; //debounce delay time

An integer cannot assume a fractional value. It will compile as a zero. If you want 10ms put a 10 in there.

Use of **S**trings in Arduino land is discouraged due to memory fragmentation issues in the small RAM space. Use (lowercase) strings instead. There's a caveat - if just reserving the space you must allocate enough for the characters plus one more for the terminating null character, \0. Note how strings (AKA character arrays) are compared -

// compare Strings

char encdir[4]; // will hold CCW plus the terminating null
char clock[] = "CW";
char anticlock[] = "CCW";

void setup() {
  Serial.begin(115200);
  
  // display beginning values
  Serial.println(encdir);
  Serial.println(clock);
  Serial.println(anticlock);
  Serial.println();
  
  // modify encdir
  strcpy( encdir, anticlock);
  Serial.println(encdir);

  // compare encdir to something
  if (strcmp(encdir, "CW") == 0)
    Serial.println("cw");
  else Serial.println("ccw");
}

void loop() {
}

Here's my version of using the Encoder library.

//-------------------------------------------------
void dialMoved() {

  // Encoder handler -  Because the encoder library counts all
  // transitions, returned values are divided by four to
  // arrive at a single detent movement.

  static long newRawPosition, oldRawPosition;
  static int posnDelta, oldDelta;

  newRawPosition = ComboDial.read();
  encoderTurned = false; // Reset previous turned state
  if (newRawPosition != oldRawPosition)  // Detect movement
  {
    delay(3); // Cures some ills! Reduces bouncing values occurrence.

    // Verify full detent traversed by testing for divisibility by four.
    uint8_t lowTwo = 0 | (0x03 & newRawPosition); // Saves 14 bytes vs. modulo
    if (lowTwo == 0) // It is evenly divisible by four, now determine direction
    {
      encoderTurned = true;  // Detent crossed
      posnDelta = newRawPosition >> 2; // Divide by four
      // posnDelta = newRawPosition; // If single digit +- not needed
      if (posnDelta > oldDelta)
      {
        isEncoderDirectionCW = true;
      }
      else {
        //        Direction = CCW;
        isEncoderDirectionCW = false;
      }
      oldDelta = posnDelta;
    }
  }
  oldRawPosition = newRawPosition;
}

Two *boolean * signals come out of it, encoderTurned and isEncoderDirectionCW. Using these two you can increment/decrement a counter or sense direction/movement.

In your case, if the motor is commanded to run and encoderTurned hasn't been active for xx time a jam is indicated.

Timmcg55:
Without a signal to stop it never will." What about the digitalWrites in my encoder section. Do they not count?

Guessing that LOWs on in1 and in2 will stop the motor. I see that applied only in the counter section where the preset is reached. The motor controls, that I see, only reverse direction.

Timmcg55:
I cannot poll the pins since they remain in the same state when the motor is stalled. Polling the encoder ++ signal has got me nowhere.

I'm missing something. What is the purpose of polling the encoder?

Post your latest version, formatted as suggested.

Doug,
You asked "I'm missing something. What is the purpose of polling the encoder?". I thought I could detect when it had stopped counting to trigger a reversing of the motor.

My current code is attached. I made some of the changes you mentioned. I'll need some time to digest the rest of the code you wrote. Just to better understand what is happening and how, plus how to incorporate it into mine, and what exactly it would replace in mine.
Thanks for the assist,

Tim

#include <Encoder.h>
#include <Keypad.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(22,23,24,25,26,27); // Mega 2560 - pins for RS,E,D4,D5,D6,D7,respectively
//KEYPAD***************
const byte ROWS = 4; 
const byte COLS = 4; 
char hexaKeys[ROWS][COLS] =         
{ 
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'0','0','0','D'}                 // changed the * & # values to 0 (zero)
};  
byte rowPins[ROWS] = {35,34,33,32};  //connect to the row pinouts of the keypad
byte colPins[COLS] = {31,30,29,};        //disabled the letter keys by leaving out #28.
//initialize Keypad
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

int ledPin=13;                           // on board (mega)and an external blue LED pin
int sensorPin=4;                       // break beam sensor is pin 4 - blinks blue LED pin 13
int sensorState=0,lastState=0;  // reading the sensor status
int counter=0;                         // hold the number of times the beam has been broken
int value=0;                           // hold the key press quantity desired
int cyclePin=2;                     // cycle start pushbutton connected to pin 2
int cycleState=0;                   // current cyclePin state
int lastCycleState=0;               // previous cyclePin state
//H-bridge - 12VDC motor
int enA=10;               // enable pin to mega pin 10
int in1=12;               // in1 pin to mega pin 12
int in2=11;               // in2 pin to mega pin 11
// Rotary Encoder Inputs
 int CLK=14;             // encode clock pin on pin 14 
 int DT=15;              // encoder data pin on pin 15
 int ledCCW=8;           // red LED indicates the rotary disc is moving CCW
 int ledCW=9;            // green LED indicate the rotary disc is giong CW
 int ENcounter = 0;      // EN for encoder as counter is used for counting beam breaks
 int currentStateCLK;
 int previousStateCLK;  
 String encdir ="";         // hold the encoder's current direction
 long lastDebounce = 0;     
 int delayDebounce = 10;    // debounce delay time
 int PreviousCLK;           // previous pin state 
 int PreviousDATA;          // previous pin state
 int displaycounter=0;      // current counter value
 
void setup() {
  Serial.begin (112500);
//H-bridge
  pinMode(enA, OUTPUT);
  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);
// Turn off motor - pins initial state = LOW
  digitalWrite(in1, LOW);
  digitalWrite(in2, LOW);
  analogWrite(enA, 255);            // enables motor    
  pinMode(ledPin, OUTPUT); 
  pinMode(sensorPin, INPUT_PULLUP); 
  lcd.begin(16, 2);                 
  pinMode(cyclePin, INPUT);         
//encoder setup
  pinMode (CLK,INPUT);
  pinMode (DT,INPUT);     
  pinMode (ledCW,OUTPUT);
  pinMode (ledCCW,OUTPUT);
  
  previousStateCLK = digitalRead(CLK); 
  
}

void loop() {
//debounce encoder ADDED 8/5
if ((millis()-lastDebounce)>delayDebounce)  // If enough time has passed check the rotary encoder
  { check_rotary();                         // Rotary Encoder check routine below    
    PreviousCLK=digitalRead(CLK);
    PreviousDATA=digitalRead(DT);    
    lastDebounce=millis();                  // Set variable to current millis() timer
  }
//KEYPAD 
  lcd.setCursor(0,0);                
  lcd.print("Enter Qty");            // Prompt the user for input  
  char key=customKeypad.getKey();   
  if(key){
  value=(value*10)+key-48;           // store the value of the keypress
   lcd.setCursor(10,0);                              
   lcd.print(value); }               // Print the Enter Qty
  if(key){
   digitalWrite(in1,HIGH);           // motor on CW
   digitalWrite(in2,LOW);
   }
//ENCODER - 
 currentStateCLK = digitalRead(CLK);        // Read the current state of inputCLK    
if (currentStateCLK != previousStateCLK){   // If the inputDT state is different than the inputCLK state then the encoder is rotating counterclockwise  
 if(!currentStateCLK){
  if (digitalRead(DT) != currentStateCLK) { // I switched ++ to -- and -- to ++ in this section
   ENcounter ++;
   encdir ="CW";
   digitalWrite(ledCW, LOW);
   digitalWrite(ledCCW, HIGH);       
   } else {                             // Encoder is rotating CCW (Rotary disc is going CW, which is what I want to monitor)
   ENcounter --;
   encdir ="CCW";
   digitalWrite(ledCW, HIGH);
   digitalWrite(ledCCW, LOW);       
   }
 } 
   previousStateCLK = currentStateCLK; // set previousStateCLK to the currentStateCLK
}

//SENSOR  
sensorState = digitalRead(sensorPin);  // read the state of the sensor  
  if (sensorState == LOW) {            // check if the sensor beam is broken, if it is, the sensorState is LOW
  digitalWrite(ledPin, HIGH); }
   else {digitalWrite(ledPin, LOW); }        
  if(sensorState && !lastState) { }    
    if (!sensorState && lastState) {
    counter++;      
    lcd.setCursor(0, 1);   
    lcd.print("Count:");
    lcd.print(counter);                // prints the total beam breaks      
  }
    lastState = sensorState;     
  if(counter != value);{}
   if(counter == value){
   digitalWrite(in1,LOW);              // turn motor off 
   digitalWrite(in2,LOW);
   counter=0;                          // reset counters (beam break, encoder)
   ENcounter=0;
   }
//CYCLE START 
cycleState=digitalRead(cyclePin);      // read the cycleStart pin
 if(cycleState != lastCycleState){     // compare cycleState to its previous state
  if(cycleState == HIGH){              // if the state changes to HIGH turn motor on
  digitalWrite(in1, HIGH);             // motor runs clockwise
  digitalWrite(in2, LOW);
  lcd.setCursor(6, 1);   
  lcd.print("  ");
  }
  else{}                               // if cycleState is LOW do nothing  
  delay(100);                          // delay to avoid bouncing
}
lastCycleState = cycleState;           // save the current state as the last state, for next time through the loop
}

// Check if Rotary Encoder has moved
void check_rotary() {          
 if ((PreviousCLK == 0) && (PreviousDATA == 1)) {
    if ((digitalRead(CLK) == 1) && (digitalRead(DT) == 0)) {
      displaycounter++;       
      }      
    if ((digitalRead(CLK) == 1) && (digitalRead(DT) == 1)) {
      displaycounter--;        
     }  }     
if ((PreviousCLK == 1) && (PreviousDATA == 0)) {
    if ((digitalRead(CLK) == 0) && (digitalRead(DT) == 1)) {
      displaycounter++;        
      }  
    if ((digitalRead(CLK) == 0) && (digitalRead(DT) == 0)) {
      displaycounter--;        
      }  }
      
if ((PreviousCLK == 1) && (PreviousDATA == 1)) {
    if ((digitalRead(CLK) == 0) && (digitalRead(DT) == 1)) {
      displaycounter++;        
      }      
    if ((digitalRead(CLK) == 0) && (digitalRead(DT) == 0)) {
      displaycounter--;       
      }  }      
if ((PreviousCLK == 0) && (PreviousDATA == 0)) {
    if ((digitalRead(CLK) == 1) && (digitalRead(DT) == 0)) {
      displaycounter++;       
      }      
    if ((digitalRead(CLK) == 1) && (digitalRead(DT) == 1)) {
      displaycounter--;        
      } 
   }            
}

Doug said, " Guessing that LOWs on in1 and in2 will stop the motor. I see that applied only in the counter section where the preset is reached. The motor controls, that I see, only reverse direction."

Doug,
So, i was not trying to stop the motor. If it jams the pins still hold their state. The Arduino still thinks it's turning.
When it does jam I would like the motor to reverse and clear the jam.
Unless I miss your meaning.

Tim

It's occurred to me that you don't need the encoder to tell you direction - the motor outputs already give you that. All you need from the encoder is a change-of-state telling you the conveyor is turning.

So when one of the encoder inputs changes (could be false-true or true-false, or both) you generate a signal (see IDE -> file/examples/digital/state change detection) which resets a free-running timer. Set the timer preset value to a bit longer than the time between encoder changes. If the encoder stops sending pulses the timer times out and you react to that by reversing the motor.

You need to ignore the encoder timer when the motor is not running and for a brief time when starting to allow the motor to come to speed before checking for a fault.

So the motor keeps rotating until the parts count is reached.

My logic would be something like this.

if (count < MAX_COUNT and millis()-startTime > 6000)
reverse_motor();

I don't know your process but say there is 3 seconds between parts passing the beam.
basically when you detect a part start the timer. The timer is set for 6 seconds (worst case).

for normal operation every time a part is counted you just reset the counter. everyones happy.

if (a jam occurs) the next part is not detected and 6 seconds pass. This tells the processor a jam
has occured. So reverse the motor.

if you know how to use millis() then you can use a variable to store millis() value when a part is detected.

So when you increment your counter in code just add this line, it resets the startTime for the next part

startTime=millis();

Noweare,
This is something I thought about. The parts passing through the sensor do not pass at a consistent rate. The times could vary as much as 30 seconds. That's far too long to wait to reverse a jammed motor.

Thanks for your reply though. I appreciate your input.

Tim