Quadrature Enocder Simulation

cattledog,

Thanks for your post

in reply to "I think the place to start..."

I will do as you suggest.

Making up a few of these is a great way to generate the unsynchronized sources that I need to do good testing on my project.

I can see that you are a prolific forum member and always ready to help posters. i just want to thank you and to point out that once you have put in the hard work and posted, even though the original poster has long gone, people like me can benefit from it years later! Without your input i would have yet another project to divert me away from my goal.

Bob

wvmarle,

Thanks for your post.

In reply to "My approach to 90° offset would be..."

Clever idea but In my case since I want to stress test my code, I will probably want to try non 90 degree offsets that more reflect the real world. So being able to change the offset will be handy for me.

I will probable also want to inject some external noise into the signal too.

Bob,
Sydney Australia

cattledog,

Could I ask you to look at your code again?

First, I think I found a bug

The value in 'command' is not cleared if there is no serial character available. This causes the command to be continuously repeated. I added an else which clears it.

OK now the next bit is a bit fuzzy because I'm a newbie at arduino.

This is about some things that are not working in the program.
Firstly, I ran the program and the count and error count prints are always zero.
I only had a MEGA2560 at the time and after a bit of research I found the timers are a bit different. Since I had a UNO already on order I put the project on the back burner.

Now that I have a UNO. I tried again and found the same problem that there are only zero counts on the serial monitor.

Ok so here is what I know:-

  1. The quadrature signal at digital 9 & 10 is present and is 5kHz period as measured on my scope. Is 5kHz correct?

  2. The serial monitor shows zero for count and zero for errors.

  3. Forcing errorCount to increment in the ISR also shows a zero count in the serial.

  4. Commenting out the errorCount=0 doesn't change anything

So what I'm wondering is, does it really ever enter the ISR?

The code you provided looks like a snippet from some larger program and it contains stuff that I don't understand.

For instance the serial monitor section looks like it was originally intended for an external 'real' encoder because why would you need the error counting in a quadrature generator?

The external interrupts are on digital 2 and 3 aren't they? So do I need to physically link them to digital 9 & 10 to get the ISR to run?

Now here comes the question that I don't know how to find elsewhere and I hope you can educate me.

Can I generate pin change interrupts from digital 9 & 10 when they are outputs? Is it covered in the datasheet?

Bob

The external interrupts are on digital 2 and 3 aren't they? So do I need to physically link them to digital 9 & 10 to get the ISR to run?

Yes. Offset square wave outputs are on pins 9 and 10. Jumper these outputs to pins 2 and 3 to read the signals. It was in the post, but not in the comments of the sketch. Sorry. I think the program was originally developed with a second Arduino. One as a generator and one as a reader.

  1. The quadrature signal at digital 9 & 10 is present and is 5kHz period as measured on my scope. Is 5kHz correct?

When I test the program with the default setting of ICR1 = 199 I see encoder counts of 20K. I believe that the period of each square wave is 10K. The entire quadrature pattern with 4 edges repeats at 5K.

Can I generate pin change interrupts from digital 9 & 10 when they are outputs? Is it covered in the datasheet?

A pin can be an output and be triggered by an interrupt, so this may be possible. Give it a try. I have certainly read analogWrite() pwm with interrupts on the pin outputting the signal.

I have a pin change interrupt encoder sketch somewhere, and I'll see if I can put it together with the generator.

why would you need the error counting in a quadrature generator?

It's actually for counting errors in the encoder reading side of the sketch. When you get to high speeds, you will see errors. The reading software does not keep up with the generating software and begins to miss pulses. I have tried different reading algorithms with the generator to see which performs the best.

Thank you cattledog.

That has cleared up the mystery. You are correct I didn't read the posts or follow the links properly, I just read the code.

Bob

Here is the Timer1 Quadrature Generator(quadrature output on D9 and D10) with Pin Change Interrupt encoder readings on those same pins.

The quadrature output triggers a pin change interrupt. No need to jumper pins or provide external input to interrupt pins.

//Quadrature signal generator two outputs offset 90 degrees
//emulate rotary encoder
//Timer 1 CTC with ICR1 TOP
//Switch direction with Serial input F/R/S Enter Capital F S or R with not line ending
//Pin Change Interrupts on Timer Output Pins

const char encTable[16] = {0, 1, -1, 0, -1, 0, -0, 1, 1, 0, 0, -1, 0, -1, 1, 0}; //gives -1, 0 or 1 depending on encoder movement
volatile long encoderCount;
volatile long errorCount;
volatile byte encState;
unsigned long prevDisplay;
unsigned long interval = 1000;

char Command = 'F'; //default state is Forward

void setup() {

  Serial.begin(115200);

  pinMode(9, OUTPUT); //output A
  pinMode(10, OUTPUT); //output B

  //Enable pin change interrupts on pins 9 and 10 PB 2 and 3
  PCICR |= (1 << PCIE0); //enable group interrupts on PORTB PCINT[7:0]
  PCMSK0 |= (1 << PCINT2); //enable interrupt pin 10
  PCMSK0 |= (1 << PCINT1); //enable interrupt pin 9

  TCCR1A = 0; //clear timer registers
  TCCR1B = 0;
  TCNT1 = 0;
  GTCCR |= 1 << PSRASY; //reset prescaler

  //ICR1 and Prescaler sets frequency
  //no prescaler .0625 us per count @ 16mhz
  //prescaler 8 .5 us per count

  TCCR1B |=  _BV(CS11); // prescaler 8
  //TCCR1B |= _BV(CS10); //no prescaler

  //counts are zero indexed 2edges per ICR1 period
  //numerical values for prescaler 8.
  //e.g. 10k period give 20k encoder counts

  ICR1 = 199;//10k ICR1 period  20k encoder counts
  //ICR1 = 99; //20k ICR1 period 40k encoder counts
  //ICR1 = 49; //40K ICR1 period 80k encoder counts
  //ICR1 = 46; //42.5K ICR1 period 85k encoder counts
  //ICR1 = 41; //47.5k ICR1 period 95K encoder counts
  //ICR1 = 39; //50k ICR1 period 100k encoder counts
  //ICR1 = 29; //66.6K ICR1 period 133k encoder counts
  //ICR1 = 19; //100k ICR1 period 200k encoder counts
  //ICR1 = 17;
  //ICR1 = 14;
  //ICR1 = 9;

  OCR1A = ICR1 - 1; //two different pulse widths almost 100% duty cycle
  OCR1B = OCR1A / 2; //offset by half period

  TCCR1B |= _BV(WGM13) | _BV(WGM12); //CTC mode with ICR1
  TCCR1A = _BV(COM1A0) | _BV(COM1B0); //Toggle OC1A/OC1B on compare match
}
void loop () {

  readCommand();

  if (Command == 'F')
  {
    OCR1A = ICR1 - 1; //two different pulse widths almost 100% duty cycle
    OCR1B = OCR1A / 2; //offset by half period
    TCCR1B |= _BV(CS11);//start timer
  }

  if (Command == 'R')
  {
    OCR1B = ICR1 - 1; //two different pulse widths almost 100% duty cycle
    OCR1A = OCR1B / 2; //offset by half period
    TCCR1B |=  _BV(CS11);//start timer
  }

  if (Command == 'S')
  {
    TCCR1B &= ~(_BV(CS11));//stop timer
  }

  if (millis() - prevDisplay >= interval)
  {
    prevDisplay += interval;
    noInterrupts();
    long copyEncoderCount = encoderCount;
    encoderCount = 0;
    long copyErrorCount = errorCount;
    errorCount = 0;
    interrupts();

    Serial.print(copyEncoderCount);
    Serial.print('\t');
    Serial.println(copyErrorCount);
  }
}

ISR (PCINT0_vect)
{
  encState = ((encState << 2) | ((PINB >> 1) & 3)) & 15; //use encoder bits and last state to form index
  encoderCount += encTable[encState];//update actual position on encoder movement
  if (encTable[encState] == 0)
    errorCount++;
}

void readCommand() {

  if (Serial.available() > 0)
  {
    Command = Serial.read();
    Serial.println(Command);
  }
}

Thank you cattledog. Good one

Hi cattledog,

That works really well. I really owe you a beer. You just freed up half the pins!

I will get on now and think up a way of controlling the number and direction of encoder steps from a script as I discussed earlier.
It seems unlikely I will be able to make it run very fast though, except in unlimited forward and reverse motion.

Oh and I fixed that bug I mentioned earlier

void readCommand() 
{

     if (Serial.available() > 0)
         {
              Command = Serial.read();
              Serial.println(Command);
         }
     else
         { 
              Command = 0; // fix bug where command wasn't cleared
         }
}

I don't know how to add code to a post yet so please forgive me for putting this snippet it inline.

For testing I slowed everything down to 50Hz so that I could see stable counts.

The only problem I can see is that the decode produces negative counts for forward, and positive for reverse. i realise the direction is somewhat arbitrary anyway depending on which way the signals A and B are externally connected but I would like it to be internally self consistent. I'm not really sure where to fix this (generator or encoder) I will have to check a book to get the preferred definition of forward and reverse

Bob

Hi all,
I've been working away on this quadrature test generator and I'm up to the point of needing a good display. At the moment the code supports moves, of selected length, forward, reverse and oscillate. Loops to repeat the operation and speeds from 60 counts per second to 80k cps.

Originally I used the Arduino serial monitor interface as a display but I would much prefer a graphical one with buttons and text boxes (running on a windows PC)

Since I'm a newbie to all of this and don't have the experience yet to review what is out there, would someone suggest a PC language to create one with?

I will need it to be able to:-

1 Select which COM port to use from a populated list
2 Generate a serial string from a button press
3 Enter numeric values from the keyboard
4 Display messages received from serial strings
5 Have more than on copy of the program running simultaneously (using different COM ports)

I spent some time trying out a 20x2 LCD shield but with only a few buttons entering numeric values was very clunky

Bob

I've been working away on this quadrature test generator and I'm up to the point of needing a good display. At the moment the code supports moves, of selected length, forward, reverse and oscillate. Loops to repeat the operation and speeds from 60 counts per second to 80k cps.

It sounds like you have made a lot of progress. I'm curious about how do you plan to use the program when it is completed?

I would suggest that you start a new thread in the Forum section called "Interfacing w/ Software on the Computer". Your problem is now more one of user interface through a PC independent of the underlying program. You are likely to get some new eyes on the problem from people who know about PCs and Arduino's instead of encoders and timers.

Hi Cattledog,

I was hoping that you were still reading my posts. Your posts have been my inspiration.

To answer your question. One of my hobbies is metalworking and I have a number of machines for this.

I have built some magnetic linear position encoders based on the AS5311 IC. I wish to mount the linear encoders to my lathe etc, and then attach displays giving the position of each axis. This is commonly called a DRO (digital read out). The linear encoders have a quadrature signal output. The design of the display is my main project.

Since I may have several quadrature encoders connected, and as each of them will be asynchronous to the others, using a quadrature generator (or more likely generators) for testing seems like a good idea.

I will start a new thread about my interface question as you suggest.

Back onto this topic..

When I've finished the quadrature simulator code and want to share it with others, should I put it on Github or paste the code into a post?

Bob

Bob-

Thanks for the information about your project. Simplifying the test environment is good practice.

My grandfather was a tool and die maker in the overhead belt days, and my father owned a large machine shop so I am happy to be involved in metal working projects.

When I've finished the quadrature simulator code and want to share it with others, should I put it on Github or paste the code into a post?

Certainly you should post your completed code, or perhaps the serial monitor version, in this thread.

There is also a forum section called "Exhibition/Gallery" where completed projects are often posted.

Github is a good choice for hosting the code somewhere other than this website. It will be useful if you are directing people from metal working interest groups to your code and linear encoder projects.

Hi Cattledog,

I gave up the idea of having a PC based application to drive the QTG as it didn't look like it was really worth the effort.

So here is the serial controlled final version.

I hope you get a chance to try it out.

I attached the file as it was above the 9000 character posting limit

Bob

Quadrature_test_generator.ino (16.5 KB)

Thanks for posting the completed code. You have put good effort into making the program more flexible, and I hope that you are finding it useful for your display development.

I have one observation on what you have done.

One of the the original ideas behind the code was to benchmark encoder reading algorithms or test programs reading encoders. The generator was running on different Arduino from the unit running the encoder reading software. Errors and speed were important.

What you have done is to place the distance targets and incrementing/decrementing them within the isr and they will always be accurately met. You really don't test for missed counts if the encoder reading algorithm is slow or if interrupts are missed. This would be important if polling, rather than interrupt encoder reading algorithms were used, or if the quadrature interrupt isr were improperly lengthy and counts were missed while interrupts are disabled when the isr is executing.

I think you can make the distance targets independent of the quadrature reading isr by using an overflow interrupt on Timer1 so that each time it reaches ICR1 you record a count and use that count to verify the algorithm and show that no pulses are missed. You would have a count derived from the hardware timer, and a count derived from the isr or other reading routine. The two values should match if the reading algorithm is fast and accurate.

This use of the quadrature generator may be of no use to you as I doubt that the displays are interfering with the interrupt based counts and the algorithm used.

Cattledog,

Thank you for taking the time to comment.

I didn't properly think through your earlier (and this times) comments about using the overflow of the timer to record counts. I will look into adding it. What was the highest speed you got it to work?

Your comments about the speed and errors are noted. In an earlier version I did keep track of errors in the ISR but after some thought I replaced it with a error flag instead. The reasoning being that the amount of errors isn't very useful to me and also that the change made the ISR run faster. I can also compare the value on the DRO display to the QTG and they should match. That was the inspiration for the oscillate mode. So that I can leave the test looping overnight and the numbers should match when the loops end. Because the speeds of linear encoders are more manageable than rotary ones the QTG should be OK, but time will tell.

I think your idea of the encoder & decoder in the same micro is a stroke of genius. I would have never thought that up in a lifetime!

Now I need to start using the QTG...

Bob

PS Did you ever consider using an FPGA for this?

I sent my code to Visustin to get a flowchart made. (Flowcharting service – Free for 3 files)

It looks really pretty and it unravels my spaghetti code nicely.

Attached is the pdf that they sent me.

Quadrature_test_generator V1.0 flowchart.pdf (28.4 KB)

Hello, friends!
Somebody can tell how to adapt the code for encoder emulation for timer0. I need to emulate 2 encoders at the same time.

Thank you in advance...

how to adapt the code for encoder emulation for timer0. I need to emulate 2 encoders at the same time.

The quadrature emulation code in this thread is written for a 16 bit timer where the frequency can be set with ICRn.

For multiple instances, you will need to use an Arduino with multiple 16 bit timers like the MEGA. As far as I know, Timer0 is an 8 bit timer on both the Uno and the Mega and can not be used for the quadrature generator.

Hi Scorpkolev,
If you want the simulator to have two channels then as Cattledog says you need another 16 bit timer. I needed 4 channels for the original project. It was easy to do by using extra arduino UNOs one for each channel all running the same code. This method has the added benefit that the channels are asynchronous to each other and so you can uncover more problems in your testing. When finished I just recycled the Arduinos for the next project.

I don't have a need to improve the simulator, but if I did, I would use the new Arduino Vidor, and use the FPGA part, as a multiple generator.

Hats off the Cattledog for helping me originally get this going...

Hi Scorpkolev

I've just seen the web page for the new arduino MKR Vidor 4000 that has an FPGA on it.
There is a sketch to do 11 simultaneous quadrature encoder inputs at over a million pps.

I think if it was reprogrammed as a simulator, it would do any speed you are likely to encounter.

Bob