Using quadrature encoder to generate RC compatible PWM

I am trying to use a handwheel direct-drive attached to quadrature encoder to replace the stick of an RC radio. (servo library driving into the radio's trainer port). So I spin the wheel fast, it's equivalent to pushing the RC radio stick hard. Spin slow and it's like barely pushing on the stick. In my usage, it's more important to read slow than fast - I can only hand-crank the encoder so fast - let's say 300rpm.

Here's what I've (basically) figured out::

-Quadrature encoder w/ interrupts return a direction and pulse count.
-RC PWM is direction and amount of that direction 0-255 (servo.h)

-I think I need to read the quadrature as "instantaneous" velocity? This can be scaled and output to the servo function.

I am NEW to interrupts and overall a novice, so any help reasoning through this would be VERY appreciated thank you!

The usually don’t do this. They as the name implies return a quadrature signal. This is two signals that are 90 degrees out of phase, so there are four states. These are subject to contact bounce so you need a state machine to determine when a full set of transitions have been received. Only then can you consider they have generated one step. On its own that just tells you it has moved. It tells you nothing about the velocity.

Thanks Grumpy_Mike.

That explains why I've been having trouble figuring out how to make it work.

So I need to poll it over time. I have some 600 pulse/revolution encoders to test this, and ideally I can measure every 10 ms or faster to calculate velocity. If I understand correctly, I need direction * (new pulses)/10ms

Maybe?

/*
*Quadrature Decoder
*/
#include "Arduino.h"
#include <Servo.h>


// Quadrature encoders
// Left encoder
#define c_LeftEncoderInterruptA 0
#define c_LeftEncoderInterruptB 1
#define c_LeftEncoderPinA 2 // pin 2 quad cha
#define c_LeftEncoderPinB 3 // pin 3 quad chb
#define LeftEncoderIsReversed

volatile bool _LeftEncoderASet;
volatile bool _LeftEncoderBSet;
volatile bool _LeftEncoderAPrev;
volatile bool _LeftEncoderBPrev;
volatile long _LeftEncoderTicks = 0;

#define SAMPLE_DELAY (10)        //  adjust the delay to fit your needs
#define PULSES_PER_TURN (600)  //  state changes per turn on 1 line,
unsigned int lastTime;
volatile int pulseCount;
volatile bool lastState;
float rpm;


int servo_PIN=9;
Servo myservo;  // create servo object to control a servo
int angle = 90;    // initial position of the servo
int rotateAmount = 10; //rotate amount of each step

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

  myservo.attach(servo_PIN);  //make servo object

  // Quadrature encoders
  // Left encoder
  pinMode(c_LeftEncoderPinA, INPUT);      // sets pin A as input
  digitalWrite(c_LeftEncoderPinA, HIGH);  // turn on pullup resistors
  pinMode(c_LeftEncoderPinB, INPUT);      // sets pin B as input
  digitalWrite(c_LeftEncoderPinB, HIGH);  // turn on pullup resistors
  attachInterrupt(c_LeftEncoderInterruptA, HandleLeftMotorInterruptA, CHANGE);
  attachInterrupt(c_LeftEncoderInterruptB, HandleLeftMotorInterruptB, CHANGE);
}

//LOOOOOOP


void loop()
{
  if ((unsigned int)millis() - lastTime >= SAMPLE_DELAY)  
    {
  int pulses;
         noInterrupts();
         pulses = _LeftEncoderTicks;  // the atmega is 8 bits, you must disable interruts
         pulseCount = 0;       // to read/write 16 bit values accessed by ISR
         interrupts();         // re-enable interrupts.

                               // 60000 milliseconds in 1 minute for rpms.
         rpm = (pulses * (6000.f / ((unsigned int)millis() - lastTime))) / PULSES_PER_TURN;
         angle = map(rpm, -700,700,0,180);
         myservo.write(angle);
               
         Serial.print("RPM: ");
         Serial.print(rpm);
         Serial.print("Angle: ");
         Serial.print(angle);
         //Serial.print("Ticks");
         //Serial.print(_LeftEncoderTicks);
         Serial.print("\n");
         lastTime = (unsigned int)millis();
         rpm=0;
         _LeftEncoderTicks = 0;
 

  }
}


// Interrupt service routines for the left motor's quadrature encoder
void HandleLeftMotorInterruptA(){
  _LeftEncoderBSet = digitalRead(c_LeftEncoderPinB);
  _LeftEncoderASet = digitalRead(c_LeftEncoderPinA);
 
  _LeftEncoderTicks+=ParseEncoder();
 
  _LeftEncoderAPrev = _LeftEncoderASet;
  _LeftEncoderBPrev = _LeftEncoderBSet;
   

}

// Interrupt service routines for the right motor's quadrature encoder
void HandleLeftMotorInterruptB(){
  // Test transition;
  _LeftEncoderBSet = digitalRead(c_LeftEncoderPinB);
  _LeftEncoderASet = digitalRead(c_LeftEncoderPinA);
 
  _LeftEncoderTicks+=ParseEncoder();
 
  _LeftEncoderAPrev = _LeftEncoderASet;
  _LeftEncoderBPrev = _LeftEncoderBSet;
 
 
}


int ParseEncoder(){
  if(_LeftEncoderAPrev && _LeftEncoderBPrev){
    if(!_LeftEncoderASet && _LeftEncoderBSet) return 1;
    if(_LeftEncoderASet && !_LeftEncoderBSet) return -1;
  }else if(!_LeftEncoderAPrev && _LeftEncoderBPrev){
    if(!_LeftEncoderASet && !_LeftEncoderBSet) return 1;
    if(_LeftEncoderASet && _LeftEncoderBSet) return -1;
  }else if(!_LeftEncoderAPrev && !_LeftEncoderBPrev){
    if(_LeftEncoderASet && !_LeftEncoderBSet) return 1;
    if(!_LeftEncoderASet && _LeftEncoderBSet) return -1;
  }else if(_LeftEncoderAPrev && !_LeftEncoderBPrev){
    if(_LeftEncoderASet && _LeftEncoderBSet) return 1;
    if(!_LeftEncoderASet && !_LeftEncoderBSet) return -1;
  }
}

No you let each rotary encoder generate an interrupt and let the ISR ( interrupt service routine ) handle the count accumulation.
There are libraries to help you do this.
I use the Encoder.h library by Paul Stoffregen find it in the menu Sketch-> Include Library -> Manage Library.

Then it is up to you to see how many counts you have had in x seconds or the time between a new count and the last count to get the instantaneous speed.

No that is taken care of by the count going up or down.

the standard encoder-library works only reliably wit optical encoders but unreliable with encoders that have mechanical switches.

The library newEncoder uses a different and much more reliable software-technique which is rock-solid even with the most cheapest mechanical-switch encoders.

I guess if your encoder hs 600 pulses per revolution it is an optical one.

best regards Stefan

That was not the one I recommended.