Slowing down rotary encoder using interrupts

Hello,

I Am currently building a DDS Frequency Generator using an Arduino and a AD9835 http://arduino.cc/forum/index.php?topic=67958.0 I got from Spark fun.

I am using a 3x4 matrix keypad to set the output frequency and a rotary encoder for fine tuning.. I have most of this working except the rotary encoder is way to sensitive. I am using hardware debouncing found here Arduino Playground - RotaryEncoders and this works great in that the decoder increments in single steps. My problem is that I am using interrupts to control the rotary encoder and am using a large dial type knob on the encoder and find it very hard to make small adjustments to it. When I spin at a reasonable pace the frequency is going up too fast. I have never used interrupts in the past but read that there is no way to put in a delay in the code. how do I go about slowing down the interrupts so it only reads every 2nd or 3rd pulse from the encoder? Can this be done in softwre or do I need to use some kind of hardware methord to slow it down.

I have attached my encoder and keypad section of the code below. any help or comments would be appreciated

/**
 * sinusoid Frequency generator.
 *
 * This program produces a sinusoidal signal using the AD9835 IC 
 */

// variables will change:
long Rotate=10000;
long Frequency =0;
int jump = 1;
long num = 0;
//*********************************************************LCD Setup Start******************************************
#include <SoftwareSerial.h>

#define txPin 5

SoftwareSerial LCD = SoftwareSerial(0, txPin);
// since the LCD does not send data back to the Arduino, we should only define the txPin
const int LCDdelay=10;  // conservative, 2 actually works

//*************************************AD9835 setup*************************************

#include <SPI.h>
#include "AD9835.h"

//setup Keypad Pins\

#include <Keypad.h>
const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
  {
    '1','2','3'                                                              }
  ,
  {
    '4','5','6'                                                              }
  ,
  {
    '7','8','9'                                                              }
  ,
  {
    '*','0','#'                                                              }
};
byte rowPins[ROWS] = {
  48, 46, 44, 42}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {
  40, 38, 36}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

// We begin by creating the AD9835 object with the pin assignments
// that are used.  If another pinout is used, this must be
// modified.

//First We must first set up all of our DDS AD9385 output pins.
AD9835 dds(
13, // FSYNC
9, // SCLK
8, // SDATA
12, // FSEL
11, // PSEL1Arduino Serial USB Board 
10, // PSEL0
50000000 // hzMasterClockFrequency (50MHz)
);


//***********************************Encoder setup Start**********************************
#define encoder0PinA  2
#define encoder0PinB  4


  //*************************************Encoder Setup**************************************
  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor
  attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2

}//end setup

void loop(){



  while(1)
  { 
    InputKeys();
    SerialSend();


//*************************************Keypad Setup***************************************
void InputKeys(){

  char key = keypad.getKey(); 
  if (key){
    switch (key) {
    case '#': 
      Frequency = num ;//Set Frequency to number entered
      dds.setFrequencyHz(0, (Frequency)); //Send new Frequency to DDS IC
      dds.selectFrequencyRegister(0);

      // Without modulation the choice of phase offset does not matter,
      // but we set it to zero for good measure.
      dds.setPhaseDeg(0,0);
      dds.selectPhaseRegister(0);
      Serial.println("---------------------");
      Serial.println("the frequency is");
      Serial.print(Frequency);
      Serial.print(" Hz");
      Serial.println("");
      Serial.println("--------------------");
      num=0;
      clearLCD();
      delay (100);
      break;

    case '*'://clear input
      num=0;
      clearLCD();
      break;

    default:
      key=key-'0'; //convert from Char to integer otherwise will be out by 48
      num =(num*10 + key); //move left 1 decimal place and enter next digit
      //Serial.println(num); //print number 
      Serial.println(num); //print number 
      break;
    }//end switch

  }//end if
  delay(10);

}//end keypad subrutine 


//************************************Rotary Encoder**************************************
void doEncoder(){


  if (digitalRead(encoder0PinA) == HIGH) {   // found a low-to-high on channel A
    if (digitalRead(encoder0PinB) == LOW) {  // check channel B to see which way
      // encoder is turning


      Rotate = Rotate;                 // CCW
    }//end  if (digitalRead(encoder0PinB) == LOW)
    else {
      Rotate = Rotate;                 // CW
    }//end else
  }//end if (digitalRead(encoder0PinA) == HIGH)
  else                                        // found a high-to-low on channel A
  { 
    if (digitalRead(encoder0PinB) == LOW) {   // check channel B to see which way
      // encoder is turning  
      Rotate = Rotate + jump;        // CW
    }//end if 

    else {
      Rotate = Rotate - jump;         // CCW
    }//end else
  }//end else if (digitalRead(encoder0PinB) == LOw

}//end subroutine


void SerialSend(){

  //***************************************DDS STUFF**************************************
 
  //clearLCD();
   
Frequency =  Rotate;
  
  // Start the dds Libary 
  dds.begin();
  // We then set the board to produce a signal.
  dds.setFrequencyHz(0, (Frequency));
  dds.selectFrequencyRegister(0);

  // Without modulation the choice of phase offset does not matter,
  // but we set it to zero for good measure.
  dds.setPhaseDeg(0,0);
  dds.selectPhaseRegister(0);

  // Finally, we turn on the IC.
  dds.enable();

 
}

how do I go about slowing down the interrupts so it only reads every 2nd or 3rd pulse from the encoder?

Interrupts are, by definition, high priority events ("If you don't read this character NOW out of my register, it'll be overwritten in a few hundred microseconds"); you don't ever want to delay in them, unless you really know what you're doing.
What you could do is divide the incoming pulses, simply by counting them, and only outputting a pulse every three or four interrupts.
Don't forget to reset the counter.

You neet to declair any variable used in AB ISR as volatile.
Simply divide rotate by 4 before you use it.

The quickest thing you can try is to change the interrupt mode, that will change the step speed in half. You will have to see if that is improvement enough or not.

So change:

attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2

To:

attachInterrupt(0, doEncoder, FALLING);  // encoder pin on interrupt 0 - pin 2

Good luck

For a really simple fix, make "jump" and "rotate" floats and increment by fractions instead of integers. So, on clockwise, add 0.2. On counter-clockwise, add -0.2. When setting your frequency, just truncate the value to an integer on assignment.

Like so:

// Definitions
float rotate = 10000.0;
float jump = 0.2;

// In the ISR:
rotate = rotate + (or -) jump;

// In Serial Send
frequency = (long)rotate;

It might not be the purest solution, but it has advantages: Minimal code changes, it's configurable, and the in-between turns (between 0.0 and 1.0) are tracked with equal resolution. I.E., since your counter tracks and remembers the fractional changes, you don't end up in a situation where three CW turns are undone with a single CCW turn.

Thank you all for your fast replys and great advise. I now have it running alot smother and more fine control :slight_smile:

retrolefty:
The quickest thing you can try is to change the interrupt mode, that will change the step speed in half. You will have to see if that is improvement enough or not.

I Did this and it did help but found it was still little fast.

AWOL:

What you could do is divide the incoming pulses, simply by counting them, and only outputting a pulse every three or four interrupts.
Don't forget to reset the counter.

I did this aswell and found combination of the too did the trick nicely.

im not sure if how I went about doing it in my interrupt code is best method. any feedback would be great

//************************************Rotary Encoder**************************************
void doEncoder(){

  if (digitalRead(encoder0PinA) == HIGH) {   // found a low-to-high on channel A
    if (digitalRead(encoder0PinB) == LOW) {  // check channel B to see which way
      // encoder is turning

      Rotate = Rotate;                // CCW
      
    }//end  if (digitalRead(encoder0PinB) == LOW)
    else {
      Rotate = Rotate;                 // CW
     
    }//end else
  }//end if (digitalRead(encoder0PinA) == HIGH)
  else                                        // found a high-to-low on channel A
  { 
    if (digitalRead(encoder0PinB) == LOW) {   // check channel B to see which way encoder is turning  
      Count ++;
      if (Count >= 5) {
        Rotate = Rotate +jump;        // CW
        Count =0;
      }//end Count if
      
          }//end if 

    else {
      Count ++;
      if (Count >= 5) {
        Rotate = Rotate -jump;         // CCW
        Count =0;
      }//end Count if



    }//end else
  }//end else if (digitalRead(encoder0PinB) == LOW

}//end subroutine

Moderator edit: corrected tags (hopefully)