Rotation Counting with an EMS22A Absolute Encoder

Hi,
I have a Problem with my EMS22A Magnetic Rotary Encoder.

The datasheet says that bits S4 and S5 are used to output an Increase or Decrease in Magnitude (Rotation Count) but I am only getting 0's on all status bits.

I don't understand why they are blank, did I really buy the wrong Version?
(They are 40 Euros a piece and i can't afford to replace it in this Timeframe...)

I have a rotating FlyWheel inside the Midi Controller that I am building which swings like a Pendulum but can be rotated around (User Input) and i need to be able to sense the rotation count since the Sensor is located on the Side of the Wheel (One rotation of the Wheel is multiple rotations on the sensor...)

The absolute position data comes in fine and works amazingly well with this code.

This is the Code (Ignore the MIDI Stuff)

#include <MIDI.h>

const int PIN_CS = 5;
const int PIN_CLOCK = 6;
const int PIN_DATA = 7;

int lastAnalogValue = 128;

void setup() {
  MIDI.begin(4);
  Serial.begin(115200);

  //SETUP BOURNS EMS22A
  pinMode(PIN_CS, OUTPUT);
  pinMode(PIN_CLOCK, OUTPUT);
  pinMode(PIN_DATA, INPUT);

  digitalWrite(PIN_CLOCK, HIGH);
  digitalWrite(PIN_CS, LOW);
}


//byte stream[16];
void loop() {

  //READ BOURNS
  digitalWrite(PIN_CS, HIGH);
  digitalWrite(PIN_CS, LOW);
  
  int pos = 0;
  for (int i=0; i<10; i++) {
    digitalWrite(PIN_CLOCK, LOW);
    digitalWrite(PIN_CLOCK, HIGH);
   
    byte b = digitalRead(PIN_DATA) == HIGH ? 1 : 0;
    pos += b * pow(2, 10-(i+1));
  }

  for (int i=0; i<6; i++) { //READ STATUS BITS
    digitalWrite(PIN_CLOCK, LOW);
    digitalWrite(PIN_CLOCK, HIGH);
    
    Serial.print(digitalRead(PIN_DATA));
  }
  digitalWrite(PIN_CLOCK, LOW);
  digitalWrite(PIN_CLOCK, HIGH);
  
  Serial.print(" ");
  Serial.print(pos);
  Serial.println("");
  
  //MAP TO MIDI 127
  int analogValue = map(pos, 0, 1023, 0, 127);

  // potentiometer could be too sensitive and
  // give different (+-1) values.
  // send CC only when the difference between last value
  // is more than 1 
  
  if ((analogValue-lastAnalogValue) > 1 || (analogValue-lastAnalogValue) < -1) {
    // value changed?
    if (analogValue != lastAnalogValue) {
      // send serial value (ControlNumber 1, ControlValue = analogValue, Channel 1)
      // more info: http://arduinomidilib.sourceforge.net/a00001.html
      
      //MIDI.sendControlChange(1, analogValue, 1); //Uncomment for MIDI!!!!!!!
      
      lastAnalogValue = analogValue;
    }
  }
}

Are there any halfway reliable workarounds for counting the rotations with an absolute encoder?
Some software workaround?
Shouldn't I be able to read those status bits according to the datasheet?

I'd be happy about some advice!!

Sincerely,
antion

Shouldn't I be able to read those status bits according to the datasheet?

If your digitalWrite LOW/HIGH clocking scheme is getting the angular position data it should be able to get the status data. It's interesting that the digitalWrite() toggle appears to meet the clock timing spec.

What happens when you try to read out the 16 bits into a byte array with out interrupting the clocking scheme? It looks like you were headed in this direction with "byte stream[16]".

Can you see status data?

I've stripped out the midi code and wrote a test sketch to read the data into one array, and then break it back out into a position value 0-1023 and the six status values. I've used a test array for debugging and demonstration. You should be able to see what to modify in the sketch in order to read real data.

It's possible that the inline code you were using with pow() was making your timing work so if this code totally fails it could be that.

const int PIN_CS = 5;
const int PIN_CLOCK = 6;
const int PIN_DATA = 7;

int pos;
byte stream[16] = {0};//replace testStream with stream for actual data
//byte testStream[] = {1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1}; //test data stream
//byte testStream[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}; //test data stream
void setup() {
  Serial.begin(115200);

  //SETUP BOURNS EMS22A
  pinMode(PIN_CS, OUTPUT);
  pinMode(PIN_CLOCK, OUTPUT);
  pinMode(PIN_DATA, INPUT);

  digitalWrite(PIN_CLOCK, HIGH);
  digitalWrite(PIN_CS, LOW);
}

void loop() {
  //READ BOURNS
  digitalWrite(PIN_CS, HIGH);
  digitalWrite(PIN_CS, LOW);

  stream[16] = {0};
  for (int i = 0; i < 16; i++) {
    digitalWrite(PIN_CLOCK, LOW);
    digitalWrite(PIN_CLOCK, HIGH);

    stream[i] = digitalRead(PIN_DATA);
  }

  digitalWrite(PIN_CLOCK, LOW);
  digitalWrite(PIN_CLOCK, HIGH);

  //extract 10 bit position from data stream use testStream
  pos = 0; //clear previous data
  for (int i = 0; i < 10; i++) {
    pos = pos << 1;
    //pos += testStream[i]; //replace testStream with stream
    pos += stream[i];
  }
  //print position 0-1023
  Serial.print(pos, BIN);
  Serial.print('\t');
  Serial.println(pos);

  //extract 6 bits of status
  for (int i = 10; i < 16; i++)
    //Serial.println(testStream[i]);//replace testStream with stream
    Serial.println(stream[i]);
  delay(2000);
}

Hi,
thanks so much for your help!

It was a good idea trying to read before calculating the position.
Sadly there is still no output on those status bits :frowning:

I forgot to mention that the last bit, the parity bit (P1) does change!

This is the output (with no delay) when going through the Zero Point:

110 6 <- position
1
0
0
0 <- S4 Increase in Magnitude
0 <- S5 Decrease in Magnitude
1
101 5
1
0
0
0
0
1
100 4
1
0
0
0
0
0
10 2
1
0
0
0
0
0
1 1
1
0
0
0
0
0
1111111111 1023
1
0
0
0
0 <- WHY??? :(((
1
1111111101 1021
1
0
0
0
0
0
1111111100 1020
1
0
0
0
0
1
1111111001 1017
1
0
0
0
0
1

Interestingly, i could only get up to the value of 1020 with my sketch and thought its the encoder cutting off at 1020 but as your code goes up to 1023 it would mean i was losing the last 2 bits in my version!? o_O

Any other clues? I really thought this would be how this Encoder works and it should be this easy to acquire the data...maybe it really doesn't output magnitude in the absolute version?? :((
I wish they would be clear about this in the Datasheet.

I am still open to workarounds for counting the rotations...

thanks,
antion

I am still open to workarounds for counting the rotations...

I do not believe that there is a hardware index reading available with this encoder.

I think you are going to have to do it in software. Monitor the transitions across the boundary and which direction they are 0>1023 or 1023>0 and increment/decrement a counter. You may want to work with values like 2 and 1021 to avoid dealing with rapid transitions back and forth at the boundary.

What is your application?

Hi,
OK. that's pretty sad, i hope i can come up with a counting method that does work for my application.

Here is a very basic image of what i am trying to achieve:

Basically I am trying to calculate the angle of a large wheel on a small gearwheel running on the side of it.
The large wheel has a weight which pulls it to zero position, but it can be interacted with by hand, so it can be swung around or rotated anywhere, when you let go of it it swings like a pendulum.

It's for my industrial design diploma project and will be used as a MIDI controller. The angle of the Weight on the Large Wheel being the Output Parameter.

So the small wheel will be spinning in multiples of the large one and i am trying to figure out the angle with this encoder...if i had the magnitude i could multiply it up so eg. on a ratio of 1:4, 4 rotations of the small wheel can be mapped to one on the large wheel thus i can calculate the absolute angle of the weight on the large wheel.

The Problem i think will be, that when its spinning fast its going to lose some steps and won't detect zero-passing reliably.

Which encoder type would have been the best for my application?

The Problem i think will be, that when its spinning fast its going to lose some steps and won't detect zero-passing reliably.

Don't give up on that yet, since the wheel is gravity driven or manually driven, I don't think the counts will be too fast to follow.

Since you have an well defined initial 0 position with the weight down in a stable position, I think you could have used an incremental encoder with quadrature output rather than an absolute encoder. Having an index position signal might be helpful, but not really required. It should be just math to figure out where the big wheel is for any given count on the small wheel encoder.

The precision of angular position for the large wheel will drive the counts/revolution specification for the encoder.

For example if you need .5 degree accuracy on the big wheel position and you are at 4:1 then you will need at least 720/4 = 180 counts/revolution of the encoder on the small wheel. Encoder specifications are sometimes confusing as some manufacuturers use PPR(pulses per revolution) with 4 quadrature states available to read per pulse, and other use the state count when reading all four pulses as a counts per revolutions specification.

So i made a little sketch trying to count each and every step to see if it matches the angular data and if i could just divide the stepCount / 1024 and get the revolutions.
Of course this didn't work as when i rotate the Encoder faster the positions are not incremented by 1 but by larger values.

const int PIN_CS = 5;
const int PIN_CLOCK = 6;
const int PIN_DATA = 7;

int magnitude = 0;
int stepCounter = 0;
int oldStep = 0;

int pos;
byte stream[16] = {0};//replace testStream with stream for actual data
//byte testStream[] = {1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1}; //test data stream
//byte testStream[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}; //test data stream

void setup() {
  Serial.begin(115200);

  //SETUP BOURNS EMS22A
  pinMode(PIN_CS, OUTPUT);
  pinMode(PIN_CLOCK, OUTPUT);
  pinMode(PIN_DATA, INPUT);

  digitalWrite(PIN_CLOCK, HIGH);
  digitalWrite(PIN_CS, LOW);
}

void loop() {
  //READ BOURNS
  digitalWrite(PIN_CS, HIGH);
  digitalWrite(PIN_CS, LOW);

  stream[16] = {0};
  for (int i = 0; i < 16; i++) {
    digitalWrite(PIN_CLOCK, LOW);
    digitalWrite(PIN_CLOCK, HIGH);

    stream[i] = digitalRead(PIN_DATA);
  }

  digitalWrite(PIN_CLOCK, LOW);
  digitalWrite(PIN_CLOCK, HIGH);

  //extract 10 bit position from data stream use testStream
  pos = 0; //clear previous data
  for (int i = 0; i < 10; i++) {
    pos = pos << 1;
    //pos += testStream[i]; //replace testStream with stream
    pos += stream[i];
  }

  //COUNT STEPS UP OR DOWN
  //if(pos-oldStep > 1 || pos-oldStep < -1) { //exclude sensor jumps?
    if(pos != oldStep) {
      if(pos > oldStep) {
        stepCounter++;
        oldStep = pos;
      }
      
      if(pos < oldStep) {
        stepCounter--;
        oldStep = pos;
      }
    }
  //}
  magnitude = stepCounter / 1024;
  
  //print position 0-1023
  Serial.print(pos, BIN);
  Serial.print('\t');
  Serial.print(stepCounter);
  Serial.print('\t');
  Serial.print(magnitude);
  Serial.print('\t');
  Serial.println(pos);

  //extract 6 bits of status
  //for (int i = 10; i < 16; i++)
    //Serial.println(testStream[i]);//replace testStream with stream
    //Serial.println(stream[i]);
  //delay(500);
}

so...this is not the way to do it, i am not sure how i can track zero transitions.

Monitor the transitions across the boundary and which direction they are 0>1023 or 1023>0 and increment/decrement a counter. You may want to work with values like 2 and 1021 to avoid dealing with rapid transitions back and forth at the boundary.

eg. if the angle reaches let's say 1021 as you suggested, i then check in the next cycle whether the angle has moved to a value < 3, that would probably be a transition, but if it turns faster it won't see values 0,1,2 at all and it won't track it as a transition or I won't even see that it has moved beyond 1021...so this method seems impossible too.

Can't get my head around how to detect a transition :frowning:
I am sure this has been done before but can't find any example on the forums either.

Here is some more information from the technical brochure of the EMS22

I don't know my samplingrate ... ?
I don't know my RPM either...

eg. if the angle reaches let's say 1021 as you suggested, i then check in the next cycle whether the angle has moved to a value < 3, that would probably be a transition, but if it turns faster it won't see values 0,1,2 at all and it won't track it as a transition.

I don't think you need to see all the counts. You will sequentially test old pos against new pos. Your sample rate to read pos is basically the time for the CS pulses,the 10 digital write/reads and the binary reconstruction. You can put some serial print micro()'s in your code to see how long that takes. The 500ns timing spec(or 1 us per bit) is what is controlling the speed at which the device can be read. I would eliminate reading the status registers if there is no useful data.

I think your logic will be something like
if oldpos>1021 and newpos<3 then full rotation count++
if oldpos<3 and newpos>1021 then full rotation count--

You will always have a pos and a full rotation count value which is + or - from 0. It should be math back to the big wheel position. The greater than and less than conditional values can be changed if the fastest rotational speeds are not picked up. The problem will occur if for example you use >923 and < 100 as your test values and you move very fast backwards instead of crossing 0.

I haven't thought through all the logic involved in transition testing but I don't think you need a step counter as the 0-1023 is a "step counter" Focus on detecting transitions across 0.

i have already tried that logic you are suggesting before.

with the problem being that it won't pick up on the transition if the encoder is rotated too fast over the transition point and i "believe" that it is going to be rotating quite fast back and forth since it's a pendulum...this is subject to testing on the rig which i am building at the moment.
I must construct it in such a way, that the zero crossing on the encoder does not coincide with the gravity position of the large wheel.

here is the code for it:

const int PIN_CS = 5;
const int PIN_CLOCK = 6;
const int PIN_DATA = 7;

int magnitude = 0;

int pos;
int oldPos = 0;

byte stream[16] = {0};//replace testStream with stream for actual data
//byte testStream[] = {1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1}; //test data stream
//byte testStream[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}; //test data stream

void setup() {
  Serial.begin(115200);

  //SETUP BOURNS EMS22A
  pinMode(PIN_CS, OUTPUT);
  pinMode(PIN_CLOCK, OUTPUT);
  pinMode(PIN_DATA, INPUT);

  digitalWrite(PIN_CLOCK, HIGH);
  digitalWrite(PIN_CS, LOW);
}

void loop() {
  //READ BOURNS
  digitalWrite(PIN_CS, HIGH);
  digitalWrite(PIN_CS, LOW);

  stream[16] = {0};
  for (int i = 0; i < 16; i++) {
    digitalWrite(PIN_CLOCK, LOW);
    digitalWrite(PIN_CLOCK, HIGH);

    stream[i] = digitalRead(PIN_DATA);
  }

  digitalWrite(PIN_CLOCK, LOW);
  digitalWrite(PIN_CLOCK, HIGH);

  oldPos = pos;
  
  //extract 10 bit position from data stream use testStream
  pos = 0; //clear previous data
  for (int i = 0; i < 10; i++) {
    pos = pos << 1;
    //pos += testStream[i]; //replace testStream with stream
    pos += stream[i];
  }

  //TRANSITION TESTING!?
  if(oldPos > 1021 && pos < 3) magnitude++;
  if(oldPos < 3 && pos > 1021) magnitude--;

  //print position 0-1023
  Serial.print(pos, BIN);
  Serial.print('\t');
  Serial.print(magnitude);
  Serial.print('\t');
  Serial.println(pos);

  //extract 6 bits of status
  //for (int i = 10; i < 16; i++)
    //Serial.println(testStream[i]);//replace testStream with stream
    //Serial.println(stream[i]);
  //delay(500);
}

are there any ways to read the data faster? does changing to a higher baudrate help?
can i use some kind of interrupt technique?

thanks a lot so far, it might work out after all... :wink:

edit: by expanding the range to 1000 - 24 it works much better with higher rotation speeds! it would need a serious backward spin after entering the range to be misread as a magnitude count.

I must construct it in such a way, that the zero crossing on the encoder does not coincide with the gravity position of the large wheel.

Yes, for sure.

What happens if you widen the window

if(oldPos > 1013 && pos < 10) magnitude++;
  if(oldPos < 10 && pos > 1013) magnitude--;

Can you show of print out of the data where the transition detector misses a transition.

are there any ways to read the data faster? does changing to a higher baudrate help?
can i use some kind of interrupt technique?

Yes, you can probably read the data faster, but we always run up against the clock pulse spec and the 10 bit data. There's probably also an internal rate for the encoder to update its registers.

Serial Printing of the output is placing some delay in the loop but I think you need it now for the debugging. You can certainly change it to print out every second instead of every pass through the loop, but I'm not sure you could see that it is accurate.

You could certainly trigger regular periodic reads of the 10bit register with a timer instead of each pass through the loop. As your program grows more complex you might want to do that, but the ISR is kind of long if the pin pulsing and digitalRead()s are in there, and if it just sets a flag, you are still dependent upon loop timing.

Certainly if you were using an incremental encoder with quadrature output you would read it with interrupts and you would not be concerned with speed. Right now you are in a exercise to use your existing hardware and save some money and time.