Pin transmission interrupts missing transitions

Status
Not open for further replies.

Source code
: Inline and attached.
Error Messages: None
Hardware: Teensy3.5
Wiring: Pulse and Direction pins to a DM320T driving a stepper (this works fine). A and B signals from a rotary encoder to pins. 20V for the DM320T driver. 5V supply for the opto isolator and rotary encoder LED. Teensy powered over USB.

I'm trying to get a rotary encoder working. It provides two quadrature signals (A and B). The four high low pair combinations LL,HL,HH,LH that cycle around as the encoder moves lets the software record the movement by tracking the quadrature changes.

When sampling rising and falling edges on a binary signal, the number of ups must match the number of downs +- 1.
Also, since the A signal and B signal move with a 90 degree phase difference, the numbers of ups and downs on the A and B signals must match +- 1.

I found the encoder motion was not correctly being recorded. So I started digging. In order to help diagnose this, I attached A and B each to two pins. I set interrupts on all four pins. 2 set to record the rising edges of A and B. 2 set to record the falling edges of A and B. The position is kept, modulo 1200, which is the number of steps in 360 degrees.

An example output is this: (Postion, A rising count, A falling count, B rising count and C falling count).
POS :800
AR :1207 AF:1217
BR :1206 BF:1209

You can clearly see that the number of rising and falling edges differ by a lot more than one. Over time the position (which should be near the zero point so 1190 ish to 10ish) has drifted to 800 (positive is CCW) so it's dropping edges, rather than gaining them.

The values touched by the ISRs are volatile as they should be:
Code:
int rotary_encoder_pin_a_r = 20;
int rotary_encoder_pin_b_r = 10;
int rotary_encoder_pin_a_f = 19;
int rotary_encoder_pin_b_f = 9;

volatile int rotary_encoder_pos = 0;
volatile int br_count=0;
volatile int ar_count=0;
volatile int bf_count=0;
volatile int af_count=0;

The interrupt setup code is this:

Code:
  pinMode (rotary_encoder_pin_a_r , INPUT_PULLUP );
  pinMode (rotary_encoder_pin_b_r , INPUT_PULLUP );
  pinMode (rotary_encoder_pin_a_f , INPUT_PULLUP );
  pinMode (rotary_encoder_pin_b_f , INPUT_PULLUP );
  void do_rotary_encoder_a_r();
  void do_rotary_encoder_b_r();
  void do_rotary_encoder_a_f();
  void do_rotary_encoder_b_f();
  attachInterrupt(rotary_encoder_pin_a_r , do_rotary_encoder_a_r , RISING) ;
  attachInterrupt(rotary_encoder_pin_b_r , do_rotary_encoder_b_r , RISING) ;
  attachInterrupt(rotary_encoder_pin_a_f , do_rotary_encoder_a_f , FALLING) ;
  attachInterrupt(rotary_encoder_pin_b_f , do_rotary_encoder_b_f , FALLING) ;

The interrupt service routines are:

Code:
void do_rotary_encoder_a_r() {
  int b;
  int inc;
  ar_count++;
  b = digitalRead(rotary_encoder_pin_b_r);
  if (b == LOW) {
    inc=1;
  } else {
    inc=-1;
  }

 cli();
 rotary_encoder_pos = (rotary_encoder_pos + inc) % 1200;
 sei();
}

void do_rotary_encoder_b_r() {
  int a;
  int inc;
  br_count++;
  a = digitalRead(rotary_encoder_pin_a_r);
  
  if (a == HIGH) {
      inc=1;
  } else {
      inc = -1;
  }

  cli();
  rotary_encoder_pos = (rotary_encoder_pos + inc) % 1200;
  sei();
}

void do_rotary_encoder_a_f() {
  int b;
  int inc;
  af_count++;
  b = digitalRead(rotary_encoder_pin_b_r);

    if (b == HIGH) {
      inc=1;
    } else {
      inc=-1;
    }
 cli();
 rotary_encoder_pos = (rotary_encoder_pos + inc) % 1200;
 sei();
}

These are written to keep the critical section minimized. So the code decides whether to increment to decrement the position then applies the change in a one liner at the end, with interrupts off.

Keep in mind that each ISR runs mutually exclusive with the others because the A and B signals rise and fall only one at a time.

The falling edges appear to have been captured more reliably, but not perfectly.
The rising edges are dropped at a higher rate than the falling edges.

I looked at the waveforms on a scope and the falling edge was solid, but the rise time using the internal pullup was 4us. I reduced this to 2us with external 5k pullups. This reduced but didn't eliminate the dropping completely.

rising.jpg

I also changed my code to turn off serial comms while sampling the rotations to eliminate interrupt interference.

So there appears to be some sort of bug in the chip, where pins generating interrupts on the rising and falling edges of the same signal (rising on one pin, falling on the other) manage to miss some of the transitions. This should be impossible. It implies for example, getting two falling interrupts without an intervening rising interrupt.

A longer example of the position drifting and the counts differing is below:

Code:
POS  :388
AR  :2  AF:1
BR  :1  BF:2
---------
POS  :358
AR  :1201  AF:1211
BR  :1199  BF:1207
---------
POS  :328
AR  :1197  AF:1215
BR  :1196  BF:1199
---------
POS  :297
AR  :1196  AF:1211
BR  :1196  BF:1200
---------
POS  :271
AR  :1199  AF:1208
BR  :1197  BF:1200
---------
POS  :305
AR  :1213  AF:1233
BR  :1213  BF:1217
---------

Does anybody have experience with this sort of problem or suggestions?
Are the pin transition detector particularly sensitive to slow edges?
Did I miss something in the interrupt code?

Thanks,
David
 

Attachments

  • motor_sketch.ino
    7 KB · Views: 54
I have a very long-shot suggestion. Is it possible that the level at which a RISING interrupt is detected is less than the level which determines whether digitalRead sees zero or one? If so, perhaps when the rise time causes the interrupt, the digitalRead still sees the level as LOW. They are close enough that things usually work but very occasionally things go wong.
Just as a test try adding a 2 microsecond delay before digitalRead.

Pete
 
IMNSHFO, it's not the Teensy's fault. When I wrote my encoder class, I found it reliably working. I had even added code to alert when a new transition would happen while one interrupt was still running which never happened. I second el_supremo's idea that the rising time of your encoder is perhaps not optimal - on a Teensy running at 120MHz, 2us are 240 CPU cycles... If you cant't fix it by adapting the pull up resistors, use external schmitt-triggers.
 
I didn't know there was a hardware decoder in there. I've designed a couple myself in silicon back when I was young and feature sizes were counted in microns. Thanks.
 
Thank you el_supremo and Theremingenieur. I will make progress tonight.

Sleep is good. It clarified what was not clear the night before.
Reading the data bit following an interrupt is always going to present some race hazard for some timing model and the noisy input transition is one way to poke it badly.

So: I'll be adding a schmitt trigger on the inputs and removing the bit read and just using the transitions to move me around a 2 state quadrature state machine. I'll also try the HW decoder, but I want a SW version for portability.
 
Reducing the pullups to 1K from 5K seems to have cleaned it up and it's now catching all the transitions and staying close to the same point as it's repeatedly rotated through multiple 360 rotations. I had no schmitts in my parts box, so it'll be a few days before they come in the mail to clean it up properly.

For those interested, here's my updated ISRs that uses a state machine to track the place in the cycle and uses the edges to move the state machine, so it does no reading of the pin state, thus eliminating a race hazard.
Code:
void do_rotary_encoder_a_r() {
  int inc;
  int next_state;

  next_state = encoder_state;
  inc = 0;
  if (encoder_state==0) {
     inc=1;
     next_state=1;
  } else if (encoder_state==2) {
     inc = -1;
     next_state=3;     
  }
  else ar_error_count++;

  ar_count++;

 cli();
 rotary_encoder_pos = (rotary_encoder_pos + inc) % 1200;
 encoder_state = next_state;
 sei();
}

void do_rotary_encoder_b_r() {
  int inc;
  int next_state;

  inc=0;
  next_state = encoder_state;
  if (encoder_state==1) {
     inc=1;
     next_state=3;
  } else if (encoder_state==0) {
     inc = -1;
     next_state=2;     
  }
    else br_error_count++;

  br_count++;

 cli();
 rotary_encoder_pos = (rotary_encoder_pos + inc) % 1200;
 encoder_state = next_state;
 sei();
}

void do_rotary_encoder_a_f() {
  int inc;
  int next_state;

  inc=0;
  next_state = encoder_state;
  if (encoder_state==3) {
     inc=1;
     next_state=2;
  } else if (encoder_state==1) {
     inc = -1;
     next_state=0;     
  }
  else af_error_count++;
  af_count++;

 cli();
 rotary_encoder_pos = (rotary_encoder_pos + inc) % 1200;
 encoder_state = next_state;
 sei();
}

void do_rotary_encoder_b_f() {
  int inc;
  int next_state;

  inc = 0;
  next_state = encoder_state;
  if (encoder_state==3) {
     inc=-1;
     next_state=1;
  } else if (encoder_state==2) {
     inc = 1;
     next_state=0;     
  }
  else bf_error_count++;
  bf_count++;

 cli();
 rotary_encoder_pos = (rotary_encoder_pos + inc) % 1200;
 encoder_state = next_state;
 sei();
}

In the setup routine, it's important to start on the right state, so this code reads the pins states and puts it in the right state immediately after enabling the ISRs.

Code:
  a = digitalRead(rotary_encoder_pin_a_r);
  b = digitalRead(rotary_encoder_pin_b_r);
  
  encoder_state = ((a & (b<<1)) & 0x3);
 
The Schmitt Triggers arrived. I've added a local 5V linear regulator and Schmitt triggers on the inputs from the encoder, along my my more robust edge detection state machine code that doesn't have timing hazards and the encoder is working perfectly and the edges all look clean on my scope.
 
Status
Not open for further replies.
Back
Top