Encoder Tester - Sin/Cos and Incremental Quadrature

I broke my original encoder tester, and so it's time to make another one. Ryan from Everything Bends shows how to make a simple interpolator that turns Sin/Cos signals to incremental ones here:

http://www.everythingbends.com/2015/03/encoder-interpolation.html

So I decided to add that feature to be able to test Sin/Cos encoders as well. This time, I will be making a custom PCB. The original encoder tester used the Encoder library by PJRC:

https://www.pjrc.com/teensy/td_libs_Encoder.html

and it works very well, so I will stick with that. I am going to use a Teensy 2.0, since that runs on 5V and is made by PJRC as well. It has 4 interrupt pins (5,6,7,8), just enough for 2 encoder channels. And according to this:

https://www.pjrc.com/teensy/td_libs_SPI.html

it has a SPI channel on pins 0,1,2,3.

So I can just use a SPI OLED screen, as before.

I chose a 1.3" OLED screen. They are plentiful and cheap on eBay:

There is some guidance as to how to connect and program here:

https://4.bp.blogspot.com/-zJ0Ct7ELlbE/V2fB66ANTSI/AAAAAAAATnQ/Y9TlS9TfpNMZvL_ck_K3fs_vaCRybcplwCLcB/s1600/NodeMCU_OLED_SPI.png

and here:

https://brainy-bits.com/blogs/tutorials/connect-and-use-a-spi-oled-display

For the incremental quadrature encoder, I am only going to test the two direction channels, A/B and not the index, Z. So I can get away with a dual differential line receiver, and am going to use a uA9637, which operates on 5V. For the Sin/Cos quadrature encoder, I will use the IC-Haus iC-NV, in the identical configuration as Ryan. And I am choosing the same Wurth 615008140621 RJ45 connector I have used for the Differential Encoder Shield for KFLOP/SnapAMP and the HEDL Encoder to RJ45 Adapter Board.

Power will come from the Teensy USB port, and the whole board will operate on 5V.

I sent the board to me made by OSHPark.

It is a shared project, in case you want to make your own: https://oshpark.com/shared_projects/xxq1ufpe

The boards came back, and the only immediate problem is that the mounting holes on the OLED do not match the holes that were meant for it exactly. But one mated up well enough to put a screw trough and the pins on J5 provide additional support. Unless there are other problems, I don't see a need to redo the boards.

To test the OLED, only the Teensy 2.0 and the OLED need to be populated, so that is a good starting point. I used the U8x8lib to drive it. The example comes with a large sample of constructors:

...
//U8X8_SSD1306_128X64_NONAME_4W_HW_SPI u8x8(/* cs=*/ 12, /* dc=*/ 4, /* reset=*/ 6);    // Arduboy 10 (Production, Kickstarter Edition)
//U8X8_SSD1306_128X64_NONAME_4W_HW_SPI u8x8(/* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8X8_SSD1306_128X64_NONAME_3W_SW_SPI u8x8(/* clock=*/ 1, /* data=*/ 2, /* cs=*/ 0, /* reset=*/ 4);
//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);          
//U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/ 2, /* data=*/ 0, /* reset=*/ U8X8_PIN_NONE);           // Digispark ATTiny85
//U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8(/* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);   // OLEDs without Reset of
...

and it took a little playing around to find which one worked for my OLED. Apparently many identically looking variations are available....

This works as a test, using hardware SPI to drive the display:

#include <Arduino.h>
#include <U8x8lib.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
U8X8_SH1106_128X64_WINSTAR_4W_HW_SPI u8x8(/* cs=*/ 0, /* dc=*/ 20, /* reset=*/ 4);
void setup(void)
{
  u8x8.begin();
  u8x8.setPowerSave(0);
}
void loop(void)
{
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.drawString(2,4,"Encoder Test!");
  delay(2000);
}

Next, we simply need to instantiate two instances of Paul Stoffgrens' encoder library. Then we just repeatedly read and display the values.

// lots of good info at http://playground.arduino.cc/Main/RotaryEncoders; http://yameb.blogspot.com/2012/11/quadrature-encoders-in-arduino-done.html
// uses Encoder Library by Paul Stoffregen <paul@pjrc.com> http://www.pjrc.com/teensy/td_libs_Encoder.html
#include <Arduino.h>
#include <U8x8lib.h>
#include <Encoder.h>
#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
U8X8_SH1106_128X64_WINSTAR_4W_HW_SPI u8x8(/* cs=*/ 0, /* dc=*/ 20, /* reset=*/ 4);
// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEncoder1(5, 6);
Encoder myEncoder2(7, 8);
void setup(void)
{
  u8x8.begin();
  u8x8.setPowerSave(0);
  u8x8.setFont(u8x8_font_chroma48medium8_r);
  u8x8.clear();
  u8x8.drawString(2,0,"Sin/Cos");
  u8x8.drawString(2,1,"Encoder");
  u8x8.drawString(2,2,"Position:");
  u8x8.drawString(2,4,"Incremental");
  u8x8.drawString(2,5,"Encoder");
  u8x8.drawString(2,6,"Position:");
}
void loop(void)
{
  char str[80];
  long newPosition = myEncoder1.read();
  sprintf(str, "%ld", newPosition);
  u8x8.clearLine(3);
  u8x8.drawString(2,3,str);
  Serial.print("Sin/Cos: ");
  Serial.print(newPosition);
  newPosition = myEncoder2.read();
  sprintf(str, "%ld", newPosition);
  u8x8.clearLine(7);
  u8x8.drawString(2,7,str);
  Serial.print("Incremental: ");
  Serial.println(newPosition);
  delay(20);
 
}

Paul states an approximate maximum signal rate of 100 kHz. For a 0.0001 inch/per step linear encoder, this would be 10 in/sec maximum move rate. I find that I lose steps at speeds much lower than that, probably due to the signal quality of the connection I made. But when moving the encoder slowly, the tester works fine.

(Update: I had forgotten the terminating resistors on the Rs422 side. I since connected the tester to a 1 micron linear scale on a mill, and ran 20 inch moves at 50 in/sec speeds with no issue.)

Here a video of the test:

I also printed a quick case for the tester and then used it to test the Z axis of another project: