Using Millis with optical encoder

Hello to all!
I have modified a piece of code to read an LPD3806-400BM optical encoder, and give me a readout of 1-400 using only one of its outputs. Direction isn't important to me as I just need to be able to count by rotation either way.
Here it is...

/*
  In this example, we're using optical encoder LPD3806-400BM.
  Only OUT-B (white wire) is used and connected to pin 2 on Arduino.
  This allows a count of up to 400 pulses only.
  Connecting both outputs will give a count of up to 800 pulses!
*/

volatile long temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder

void setup() {
  Serial.begin (9600);
  pinMode(2, INPUT_PULLUP); // internal pullup input pin 2

  //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
  attachInterrupt(0, ai0, RISING);
}

void loop() {
  // Send the value of counter
  if ( counter != temp ) {
    Serial.println (counter);
    temp = counter;
  }
}

void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  if (digitalRead(2) == LOW) {
    counter--;
    if (counter == 0)
      counter = 400;
  }
  else {
    if (counter == 400)
      counter = 0;
    counter++;
  }
}

What I am trying to learn for the time being is to be able to turn on an LED when the pulse count reads 100. This LED needs to be on for 500ms or so and whilst I can get it to work using a simple "delay(500);", it will cause issues further down the road.
I've tried using the "blink without delay" code to get me started but to no avail. With these changes I'm not getting any LED turning on at 100.

/*
  In this example, we're using optical encoder LPD3806-400BM.
  Only OUT-B (white wire) is used and connected to pin 2 on Arduino.
  This allows a count of up to 400 pulses only.
  Connecting both outputs will give a count of up to 800 pulses!
*/
const int ledPin =  LED_BUILTIN;
int ledState = LOW;
unsigned long previousMillis = 0;
const long interval = 500;

volatile long temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder

void setup() {
  Serial.begin (9600);
  pinMode(2, INPUT_PULLUP); // internal pullup input pin 2
  pinMode(ledPin, OUTPUT);

  //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
  attachInterrupt(0, ai0, RISING);
}

void loop() {
  unsigned long currentMillis = millis();
  
  // Send the value of counter
  if ( counter != temp ) {
    Serial.println (counter);
    temp = counter;
  }
  if (currentMillis - previousMillis == interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
  if(counter == 100) {
    digitalWrite(ledPin, ledState);
    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    digitalWrite(ledPin, ledState);
  }
}
}
void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  if (digitalRead(2) == LOW) {
    counter--;
    if (counter == 0)
      counter = 400;
  }
  else {
    if (counter == 400)
      counter = 0;
    counter++;
  }
}

Any help very much appreciated :slightly_smiling_face:

Your code should set a boolean flag to true when the count becomes 100 then in loop(), if the boolean is true turn on the LED, save the start time and set the boolean to false. Each time round loop() test whether the period has elapsed and, if so, turn off the LED

Very much appreciated UKHeliBob!
Giving that a try now...

Thanks UKHeliBob. I have it working now...

/*
 In this example, we're using optical encoder LPD3806-400BM.
 Only OUT-B (white wire) is used and connected to pin 2 on Arduino.
 This allows a count of up to 400 pulses only.
 Connecting both outputs will give a count of up to 800 pulses! 
 */
const int ledPin =  LED_BUILTIN;
unsigned long startTime;
int lastCounterReading = 0;
int ledState;
unsigned long interval = 200;

volatile long temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder
    
void setup() {
  Serial.begin (9600);
  pinMode(2, INPUT_PULLUP); // internal pullup input pin 2 
  pinMode(ledPin, OUTPUT);
  
   //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
  attachInterrupt(0, ai0, RISING);
  }
   
  void loop() {
  // Send the value of counter
  if( counter != temp ){
  Serial.println (counter);
  temp = counter;
  }
   if (counter == 100) 
  {
    if (lastCounterReading != 100)
    {
      ledState = 1;
      startTime = millis();
    }
  }
  lastCounterReading = counter;
   if (ledState)
  {
    if (millis() - startTime <= interval)
    {
      digitalWrite(ledPin, HIGH);
    }
    else
    {
      digitalWrite(ledPin, LOW);
      ledState = 0;
    }
  }  
}
  
  void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  if(digitalRead(2)==LOW) {
  counter--;
   if (counter == 0)      
        counter = 400;
    }
    else {
      if (counter == 400)
        counter = 0;  
      counter++;
  }
  }
  
 

The only issue I have now is reading the encoder accurately. If I spin it too fast, Arduino misses 'pulse 100'. Should I use both the encoder's outputs? Would that make a difference? I could use possibly 95-100 as the trigger for the LED if needs be but I'm sure there's a better way as I'm only hand cranking the encoder. Usually these models can track a motor I think.

Try a faster baud rate - you may be filling the serial output buffer and blocking, causing you to miss 100.

Thanks wildbill! I tried that but the results are inconsistent. Maybe if I don't print to the monitor at all?

Tried that and no luck! Also, if I turn the encoder to slow it also misses the 100 for some reason!

The other problem is that you are abusing volatile variables. When you use them you need to turn interrupts off. temp doesn't need to be volatile, but you should turn them off when you use counter. In your check for 100, use temp instead.

"abusing volatile variables" sounds like a hanging offense!
Could you give me an example please wildbill?

Have it now...

 if (temp == 100)
    
  {
    if (lastTempReading != 100)

That seems to have cleared it up a treat!

Here's your hanging offense :grinning:

if( counter != temp ){
  Serial.println (counter);
  temp = counter;
  }

Counter is touched in an interrupt and it's four bytes. Consequently, it may change behind the scenes while you're looking at it. What you need is:

noInterrupts() ;
if( counter != temp ){
  Serial.println (counter);
  temp = counter;
  }
interrupts() ;

Although it would be better to move the Serial.print out of that section.

If I place this

noInterrupts() ;
if( counter != temp ){
  Serial.println (counter);
  temp = counter;
  }
interrupts() ;

into the code it still behaves as before wildbill.
Like this...

/*
  In this example, we're using optical encoder LPD3806-400BM.
  Only OUT-B (white wire) is used and connected to pin 2 on Arduino.
  This allows a count of up to 400 pulses only.
  Connecting both outputs will give a count of up to 800 pulses!
*/
const int ledPin =  LED_BUILTIN;
unsigned long startTime;
int lastCounterReading = 0;
int ledState;
unsigned long interval = 50;
int lastTempReading = 0;

volatile long temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder

void setup() {
  Serial.begin (250000);
  pinMode(2, INPUT_PULLUP); // internal pullup input pin 2
  pinMode(ledPin, OUTPUT);

  //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
  attachInterrupt(0, ai0, RISING);
}

void loop() {
  // Send the value of counter
  noInterrupts() ;
  if ( counter != temp ) {
    Serial.println (counter);
    temp = counter;
  }
  interrupts();
  if (counter == 100)
  {
    if (lastCounterReading != 100)
    {
      ledState = 1;
      startTime = millis();
    }
  }
  lastCounterReading = counter;
  if (ledState)
  {
    if (millis() - startTime <= interval)
    {
      digitalWrite(ledPin, HIGH);
    }
    else
    {
      digitalWrite(ledPin, LOW);
      ledState = 0;
    }
  }
}

void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  if (digitalRead(2) == LOW) {
    counter--;
    if (counter == 0)
      counter = 400;
  }
  else {
    if (counter == 400)
      counter = 0;
    counter++;
  }
}


However, if I do this...

/*
 In this example, we're using optical encoder LPD3806-400BM.
 Only OUT-B (white wire) is used and connected to pin 2 on Arduino.
 This allows a count of up to 400 pulses only.
 Connecting both outputs will give a count of up to 800 pulses! 
 */
const int ledPin =  LED_BUILTIN;
unsigned long startTime;
int lastCounterReading = 0;
int ledState;
unsigned long interval = 50;
int lastTempReading = 0;

volatile long temp, counter = 0; //This variable will increase or decrease depending on the rotation of encoder
    
void setup() {
  Serial.begin (250000);
  pinMode(2, INPUT_PULLUP); // internal pullup input pin 2 
  pinMode(ledPin, OUTPUT);
  
   //Setting up interrupt
  //A rising pulse from encodenren activated ai0(). AttachInterrupt 0 is DigitalPin nr 2 on moust Arduino.
  attachInterrupt(0, ai0, RISING);
  }
   
  void loop() {
  // Send the value of counter
  if( counter != temp ){
  Serial.println (counter);
  temp = counter;
  } 
   if (temp == 100)   
  {
    if (lastTempReading != 100)   
    {
      ledState = 1;
      startTime = millis();
    }
  }
  lastCounterReading = counter;
   if (ledState)
  {
    if (millis() - startTime <= interval)
    {
      digitalWrite(ledPin, HIGH);
    }
    else
    {
      digitalWrite(ledPin, LOW);
      ledState = 0;
    }
  }  
}
  
  void ai0() {
  // ai0 is activated if DigitalPin nr 2 is going from LOW to HIGH
  if(digitalRead(2)==LOW) {
  counter--;
   if (counter == 0)      
        counter = 400;
    }
    else {
      if (counter == 400)
        counter = 0;  
      counter++;
  }
  }
  
 

It works. What am I missing?

You should not use counter when interrupts are enabled. Your broken version does exactly that.

Thanks wildbill, I understand.
If I wanted to add another number for the counter to read and trigger the same LED, how might I do that?

Not sure what you're asking.

For example

if (temp == 100) 
if (temp == 200)
if (temp == 300)
if (temp == 400)
  {
    if (lastTempReading != temp)
    {
      ledState = 1;
      startTime = millis();
    }
  }

Do you need to maintain temp? You could reset it to 0 and see it count to 100 again.

Or use another counter, and reset that one. Every 100 when you fire off the LED.

Or notice that temp % 100 (modulo operator) will be zero as temp crosses the 100s marks.

if (temp % 100 == 0) …

a7

Ahaaaaa.....!

Bingo!!
Thanks a7 and wildbill a million!

The piece I don't understand about this whole thing is how this relates to MIDI, because as we well know, @goatboyrobbie posts are always about MIDI :thinking:

1 Like