Quadrature Enocder Simulation

Good day,

I’ve been attempting to simulate an AC motor position encoder with an adjustable PWM signal. For the time being I am testing code on an UNO board but eventually I will be moving to a stand alone IC, likely ATTINY44.

The goal is to produce two identical PWM signals with adjustable frequency and a +/-90 (or 90 and 270) degree offset @ 50% duty cycle. I have had success at producing the two waveforms with a pot adjusted frequency with the offset. However, the issue I am running into now is producing the 270 (or -90) degree offset. While this can easily be done with a multiple-pole switch I would like to embed as much functionality into the software as possible as I would eventually like to install it into an automated system.

What I am working for is two digital inputs control the phase shift. For example using PWM-A as the base signal activating input 1 should shift PWM-B 90 deg out of phase while input 2 should shift it 270 (or -90). While no input is active all signals should be shut down. This will simulated the motor rotational direction (Forward, Reverse, Stationary)

Below is a code I found online to produce the offset waveform that I have then modified a bit. The potentiometer input works just fine so moving past that I set a fixed value for the frequency and put a delay to monitor the FWD and REV directions on the scope. It seems to work ok-ish but it seems very clunky and like more of a brute-force method. Is there a more practical approach to switching this phase shift around?

int rawpot = A0;  // pot input
int fpot = 0; //raw pot value
int potfreq = 0;  //pot value scaled to Hz
int DIRin = 2;  //Direction input Pin 2
int DIR = 0;    //Stored State of Direction Input

void setup() {
  pinMode( 9 , OUTPUT );    // Arduino Pin  9 = OCR1A
  pinMode( 10 , OUTPUT );   // Arduino Pin 10 = OCR1B
  pinMode( DIRin , INPUT );   // Arduino Pin 2 = Direction Input
}

// prescaler of 1 will get us 8MHz - 488Hz
// User a higher prescaler for lower freqncies

#define PRESCALER 1
#define PRESCALER_BITS 0x01

#define CLK 16000000UL    // Default clock speed is 16MHz on Arduino Uno

// Output phase shifted wave forms on Arduino Pins 9 & 10
// freq = freqnecy in Hertz (  122 < freq <8000000 )
// shift = phase shift in degrees ( 0 <= shift < 180 


int setFWD( unsigned long freq , int shift ) {
   // Both outputs in toggle mode  
  TCCR1A = _BV( COM1A0 ) |_BV( COM1B0 );
  TCCR1B = _BV( WGM13 ) |_BV( WGM12 );
  OCR1A = 0;    // First output is the base, it always toggles at 0
  
  // This assumes prescaler = 1. For lower freqnecies, use a larger prescaler.
  unsigned long FWDA = (CLK / freq) / 2;    // /2 becuase it takes 2 toggles to make a full wave
  ICR1 = FWDA;
  unsigned long FWDB = (FWDA * shift) / 180UL; // Do mult first to save precision
  OCR1B = FWDB;
  TCCR1B |= _BV( CS10 ); 
}

int setREV( unsigned long freq , int shift ) {
  // Both outputs in toggle mode  
  TCCR1A = _BV( WGM13 ) |_BV( WGM12 );
  TCCR1B = _BV( COM1A0 ) |_BV( COM1B0 );
  OCR1B = 0;    // First output is the base, it always toggles at 0
  
  // This assumes prescaler = 1. For lower freqnecies, use a larger prescaler.
  unsigned long REVA = (CLK / freq) / 2;    // /2 becuase it takes 2 toggles to make a full wave
  ICR1 = REVA;
  unsigned long REVB = (REVA * shift) / 180UL; // Do mult first to save precision
  OCR1B = REVB;
  TCCR1B |= _BV( CS10 ); 
}


void loop() {
  DIR = digitalRead(DIRin);
  fpot = analogRead(rawpot);
  potfreq = (fpot*10);

  setFWD ( 500 , 90 );
  delay (3000);
  setREV ( 500 , 90 );
  delay (3000);
}

Fix the tags on your code - "code" tags, not "quote" tags.

Paul__B: Fix the tags on your code - "code" tags, not "quote" tags.

Any useful advice?

I’m not sure if this is what you are looking for, but here’s code for a timer 1, mode 12 (CTC with ICR1 TOP) , quadrature signal generator which I use for testing code written to read rotary encoders. I’ve bundled it with some code which reads the output.

Offset square wave outputs are on pins 9 and 10. Jumper these outputs to pins 2 and 3 to read the signals. There’s a bit more here http://forum.arduino.cc/index.php?topic=342499.0

I have added some simple Serial input to set Forward, Reverse, and Stop. Use capital F,R,and S with no line ending. The direction reversal is achieved by reversing the toggle points for the two output pins. Mode 12 make this simple.

//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


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);
  attachInterrupt(0, readEnc, CHANGE);
  attachInterrupt(1, readEnc, CHANGE);
 
  pinMode(9, OUTPUT); //output A
  pinMode(10, OUTPUT); //output B

  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);

  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);
  }
}

void readEnc() {
  encState = ((encState << 2) | ((PIND >> 2) & 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);
  }
}

Cool, thank you. I will try this out when I figure out where I left my board. It seems to have gotten lost in the mess of other projects :confused:

mjroman20:
Cool, thank you. I will try this out when I figure out where I left my board. It seems to have gotten lost in the mess of other projects :confused:

Typical.

cattledog,

Thank you very much for writing this quadrature generator code.

I’m writing a program that has a quadrature decoder and this will give me a good signal source.

I will probably want to add the option for setting:-

pulse counts eg F1000
and cycles F1000,R1000
and repeats F1000,R1000,I10

Does that sound feasible?

The additions will allow my decoder testing to detect missed or gained steps.

Bob

My approach to 90° offset would be to double the frequency, and have the pins react on the second tick only. That way you can easily offset one against the other, without bothering with a shift.

I will probably want to add the option for setting:-

pulse counts eg F1000 and cycles F1000,R1000 and repeats F1000,R1000,I10

Does that sound feasible?

I think the place to start would be to setup an overflow interrupt on Timer1 and use the isr to count the cycles and to use that count to turn the timer off.

I would separate the project into two pieces. One is to get the stop/start/cycle count control over what is essentially now a free running quadrature generator. The other is to develop the serial user interface to accept more than the F,R and S.

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