Making a position sensor using an encoder to compare a learned value

Hello,

I'm a new user and wanted to know the best methods for advancing my project using encoders and buttons.

OBJECTIVE:
The objective is to be able to LEARN a set value (position of rotary encoder) via a LEARN button and then use that to compare future readings. The future readings will be taken as a maximum value read while the second button is depressed.

APPARATUS DETAILS:
Measuring a height using a linear gear which engages the rotary encoder. When the apparatus is in the up position it will depress the READ button. Given that a button will have minor height changes itself I thought it would be best to simply take the maximum value and then compare that with the referenced learn button.

The learn button is used to record the reference height +/- a tolerance variable when the apparatus is in the up position reading.

Apparatus use would be: place the reference object in, raise it to the up position. The learn button would would be pressed record the value to be used thereafter. The test objects would be raised up, engage the read button, report the maximum value before lowering. If they are out of tolerance, engage buzzer

HELP AREA
The speed of the encoder will be relatively slow, should its readings be taken in the loop statement or via interrupts for accuracy? The program itself I would think is brief enough and the encoder slow enough not to cause issues.

My basic understanding of coding parameters would be:

place the learn button as an interupt

in the loop statement
While Read button is depressed, read the values and once the values reduce note the maximum as MAXVALUE, as the apparatus moves down the read button is released and MAXVALUE is compared to the LEARNEDVALUE

OR:

keep the encoder pinA&B set to read via interupts as their position is most critical and have the loop statement like this:

If LEARN Button is depressed record value

IF read button is depressed record MAXVALUE and compare with LEARNEDVALUE

IF outside of tolerance ring a buzzer

Forgive my lack of code, I am just figuring out the best way to lay this out prior to getting into the thick of things

A diagram of your "machine" would make your description much easier to understand.

Most encoders just give a relative position. Encoders that give an absolute position are considerably more expensive.

With a relative position encoder it will be necessary to re-establish the position every time the Arduino is restarted.

This is where I am not sure I understand your description. If you want to be able to switch on the Arduino, rotate the encoder to some position and, following a button-press, treat that position as ZERO and then count movements from that point onwards there should be no problem.

However if you want the Arduino to remember the zero position when it is switched off things will be considerably more complex.

As far as how to detect positions the one thing I would say is that you should not be thinking of an interrupt for the button. Humans press buttons and are humans are very slooowwww. If it was my project I would probably use an interrupt to detect the encoder pulses - but partly that is because I know how to write and test the short piece of code needed for an Interrupt Service Routine (ISR). Polling (i.e. checking using loop() ) could work just as well.

...R

The speed of the encoder will be relatively slow, should its readings be taken in the loop statement or via interrupts for accuracy

If the pulses are less than 1K /second then polling ( looking at the signals in a loop of some kind ) will be fine. Any faster then use an interrupt.

However note you will loose any absolute value each time the system is powered down, these encoders are normally relatively not absolute.

Even absolute encoders need to keep track of the number of compleat rotations.

Thanks Robin2 and Grumpy Mike. I figured a incremental encoder would be fine as I would turn on the unit every time I set the reference point. As long as it was reading relative to the new learned state I would be good.

Also, you offer good feedback on how interrupts work, human input would definitely snag. I managed to put some code together. The encoder reading was completed by others (noted in the code) and I used that to base my components from. One further question would be how the encoder interrupts will affect my While statement to determine the maximum "height" (encoder position) and also, the placement of resetting the maximum value (sensorMax) so that old readings are erased after the read button is released.

/*******Using Encoder to measure height*******/


const int readbuttonPin = 4;     // the number of the pushbutton pin
const int learnbuttonPin = 5;     // the number of the pushbutton pin
const int ledPin =  12;      // the number of the LED pin
const int buzzer = 11;       // the number of buzzer pin
int readbuttonstate = 0;         // variable for reading the pushbutton status
int learnbuttonstate = 0;         // variable for reading the pushbutton status
int learnedvalue = 0;  //variable for larned value
int tolerance = 1; //sets acceptable tolerance
int sensorMax = 0;     // maximum sensor value
int sensorValue = 0;         // the sensor value
// my variables above, encoder code by:
/*******Interrupt-based Rotary Encoder Sketch*******
  by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence
*/
static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile byte oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

void setup() {
  pinMode(ledPin, OUTPUT);  // initialize the led as output:
  pinMode(buzzer, OUTPUT);  //initialized the buzzer as output
  pinMode(readbuttonPin, INPUT); //read button as input
  pinMode(learnbuttonPin, INPUT);  //learn button as input
  pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
  attachInterrupt(0, PinA, RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
  attachInterrupt(1, PinB, RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
  Serial.begin(115200); // start the serial monitor link
}

void PinA() {
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00001100 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos --; //decrement the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00000100) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

void PinB() {
  cli(); //stop interrupts happening before we read pin values
  reading = PIND & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
  if (reading == B00001100 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
    encoderPos ++; //increment the encoder's position count
    bFlag = 0; //reset flags for the next turn
    aFlag = 0; //reset flags for the next turn
  }
  else if (reading == B00001000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
  sei(); //restart interrupts
}

void loop() {
  // read the state of the pushbutton value:
  learnbuttonstate = digitalRead(learnbuttonPin);  // check if the button is requesting learn

  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if (learnbuttonstate == HIGH) {
    // take reading
    learnedvalue = encoderPos;
  }
  readbuttonstate = digitalRead(readbuttonPin);  //check if the button is requesting a max read
  // check if the pushbutton is pressed.  If it is, call max read function
  while (readbuttonstate == HIGH) {
    digitalWrite(ledPin, HIGH); //turn on led to show reading
    sensorValue = encoderPos;  // read the current encoder position:

    // record the maximum sensor value
    if (sensorValue > sensorMax) {
      sensorMax = sensorValue;
    };
  }
  if (sensorMax > learnedvalue + tolerance || sensorMax < learnedvalue - tolerance)
    tone(buzzer, 5000, 3000);
  sensorMax = 0;  //reset sensor max to zero everytime read button is pressed
}

To make it easy for people to help you please modify your post and use the code button </> so your code looks like this and is easy to copy to a text editor. See How to use the Forum

Your code is too long for me to study quickly without copying to a text editor.

Also use the AutoFormat tool to indent your code for easier reading.

...R

would be how the encoder interrupts will affect my While statement

No effect at all.

However you might want to trigger off the edge of a push button rather than the level. Use the example "State Change" in the IDE's examples section.
Basically you remember the last state of the push button and say

if(lastState != currentState) { // button pressed or released

Thanks a million Grumpy Mike and Robin2. Now I just need the hardware and some major time to debug