Author Topic: Rotary encoder speed control, is encoder turning fast? update the counter +10  (Read 5778 times)

0 Members and 1 Guest are viewing this topic.

Offline ZeTeXTopic starter

  • Frequent Contributor
  • **
  • !
  • Posts: 610
  • Country: il
  • When in doubt, add more flux.
Hi,

I have a rotary encoder library that I have downloaded from the internet, and the code for reading the rotary encoder works perfectly but there is something that I would like to add to the code to make it better, what I want to add is "speed control" for the encoder, so for example, if I spin the encoder fast, instead of updating the counter once every reading of the encoder changing position, I would increase the counter x2 faster, this way if I'm using the encoder to set a value between 0-1000 instead of rotating the encoder 1000 times to get to the 1000 val I would only need to spin for example 100 times if I spin it fast, if I would spin it slow it would only update the encoder like usual, once per step.

So I taught about checking if the (if) function that runs in a loop that is checking if the encoder is rotating is accomplished for 5 times at least (5 updates to the counter) in less then 250ms for example, it would update the counter by 5 every step. the numbers are not critical as I can adjust them if I find the encoder too sensitive to speed.

My question is simple, How do I do this? I've tried various method like checking time elapsed and things like that, I also taught about maybe having a for loop and checking if the loop reached to 50 for example and the if statement accomplished for 5 time but I'm pretty new to arduino so I also had problems with that.

This is the code:
Quote

#include <Rotary.h>
Rotary rotary = Rotary(5, 6); // Connect Encoder To Pin 5, 6.

unsigned int counter;

void setup() {
  Serial.begin(57600);
  Serial.println("Start");
  pinMode(11, OUTPUT);
  }

  void loop() {
  unsigned char result = rotary.process();
  if (result == DIR_CW && counter < 255) { // Is the encoder rotating CW? if yes, counter ++
        counter++;
        Serial.println(counter);
  }
  //
    else if (result == DIR_CCW && counter > 0) { // Is the encoder rotating CCW? if yes, counter --
    counter--;
    Serial.println(counter);
  }
  //

   analogWrite(11, counter); // Output a voltage of the encoder reading
  }
 

Offline Kremmen

  • Super Contributor
  • ***
  • Posts: 1289
  • Country: fi
Can be done relatively easily.
All you need to do is check whether encoder pulses arrive at shorter intervals than a predefined limit. If yes then use large increment, otherwise the smaller one.
Presumably the encoder is one of these mechanical type that has relatively coarse resolution? In that case the timing can be easiest achieved just by reading successive values of the millis() counter. Otherwise you can also use the Timer1 library for much more sophisticated timing.
Nothing sings like a kilovolt.
Dr W. Bishop
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Keep your original counter.
Time the amount of time_between_each incr, or, decr.  For that timer, have a maximum limiter on it, say 100ms. (1 Sec).  From that top value, subtract +1 from it:
Speed_factor = 101 - (time_between_each_pulse);
Now, for your position:
accelerating_counter_position = ((accelerating_counter_position + counter) * speed_factor);
counter=accelerating_counter_position;

<<< Remember to Clear your timer whenever there is a encoder pulse>>>

Whats going on here is if you are turning your encoder so slow that it's more than 100ms, the accelerating_counter_position will inc or dec by 1 for each counter inc and dec by 1.

If you turn your encoder so fast that the timer is 0ms, the inc and dec of accelerating_counter_position for each pulse will be 101x per step.

This would be the means if you did not want to alter your existing rotary decoder's code directly.
You will need to use the CPU real time timer to make this work good.
You will also need to play with my factors to achieve the acceleration curve VS rotation speed you like.
Make sure that you filter out small switch debouncing noise when using a mechanical encoder in between steps.  Unlike the +/-1 inc & dec which self correct, mechanical noise now may mean and additional +100, -90 or similar now that the size of the bounce can be considered different velocity, therefor, a different amount of addition and subtraction may create a spurious reaction.


Good luck.
« Last Edit: January 13, 2017, 05:23:50 pm by BrianHG »
 
The following users thanked this post: ZeTeX

Offline MarkF

  • Super Contributor
  • ***
  • Posts: 2548
  • Country: us
You could read your encoder at a fixed rate incrementing or decrementing a count. Then when you process the encoder inputs in your main loop, raise 10 to the power of count. in affect, you will be changing your analogValue by 1, 10, 100 or 1000 based on the count you receive from the interrupt routine. (Essentially measuring the speed the encoder is turned as others have mentioned.)

Code: [Select]
// PORTCbits.RC1 = encoder phase 0
// PORTCbits.RC2 = encoder phase 1

uint8_t currP0;      // encoder phase 0 (current value)
uint8_t lastP0;      // encoder phase 0 (last value)
int8_t en0;          // encoder value (0=no change, (-)=CCW count, (+)=CW count)

double analogValue=0.0;

// Main entry
void main(void)
{
   double count;
   
   // Initialize encoder variables
   en0=0;
   lastP0=PORTCbits.RC1;
   
   // Setup interrupt here
   // (Recommend 50Hz or greater polling rate)
   
   // Enter main loop
   while (1) {
      // Encoder knob processing
      if (en0 != 0) {
         count=en0;  // save count
         en0=0;      // reset encoder value quickly
                     // so we don't miss any changes
         // DO SOMETHING WITH ENCODER COUNT
         analogValue += pow(10.0, count);                              <<<====================
      }
      // DO OTHER PERIODIC PROCESSING
   }
}

// Interrupt service entry
void isr(void)
{   
   // Read encoder knob
   currP0=PORTCbits.RC1;         // encoder phase 0
   if (lastP0 == 0 && currP0 == 1) {
      if (PORTCbits.RC2 == 1)    // encoder phase 1
         en0--;   // increment CCW count for main loop
      else
         en0++;   // increment CW count for main loop
   }
   lastP0=currP0;                // save current phase 0
}
« Last Edit: January 13, 2017, 07:30:59 pm by MarkF »
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9890
  • Country: us
You could read your encoder at a fixed rate incrementing or decrementing a count. Then when you process the encoder inputs in your main loop, raise 10 to the power of count. in affect, you will be changing your analogValue by 1, 10, 100 or 1000 based on the count you receive from the interrupt routine. (Essentially measuring the speed the encoder is turned as others have mentioned.)

Code: [Select]
// PORTCbits.RC1 = encoder phase 0
// PORTCbits.RC2 = encoder phase 1

uint8_t currP0;      // encoder phase 0 (current value)
uint8_t lastP0;      // encoder phase 0 (last value)
int8_t en0;          // encoder value (0=no change, (-)=CCW count, (+)=CW count)

double analogValue=0.0;

// Main entry
void main(void)
{
   double count;
   
   // Initialize encoder variables
   en0=0;
   lastP0=PORTCbits.RC1;
   
   // Setup interrupt here
   // (Recommend 50Hz or greater polling rate)
   
   // Enter main loop
   while (1) {
      // Encoder knob processing
      if (en0 != 0) {
         count=en0;  // save count
         en0=0;      // reset encoder value quickly
                     // so we don't miss any changes
         // DO SOMETHING WITH ENCODER COUNT
         analogValue += pow(10.0, count);                              <<<====================
      }
      // DO OTHER PERIODIC PROCESSING
   }
}

// Interrupt service entry
void isr(void)
{   
   // Read encoder knob
   currP0=PORTCbits.RC1;         // encoder phase 0
   if (lastP0 == 0 && currP0 == 1) {
      if (PORTCbits.RC2 == 1)    // encoder phase 1
         en0--;   // increment CCW count for main loop
      else
         en0++;   // increment CW count for main loop
   }
   lastP0=currP0;                // save current phase 0
}

I would think that en0 should be declared 'volatile'
 

Offline ZeTeXTopic starter

  • Frequent Contributor
  • **
  • !
  • Posts: 610
  • Country: il
  • When in doubt, add more flux.
Keep your original counter.
Time the amount of time_between_each incr, or, decr.  For that timer, have a maximum limiter on it, say 100ms. (1 Sec).  From that top value, subtract +1 from it:
Speed_factor = 101 - (time_between_each_pulse);
Now, for your position:
accelerating_counter_position = ((accelerating_counter_position + counter) * speed_factor);
counter=accelerating_counter_position;

<<< Remember to Clear your timer whenever there is a encoder pulse>>>

Whats going on here is if you are turning your encoder so slow that it's more than 100ms, the accelerating_counter_position will inc or dec by 1 for each counter inc and dec by 1.

If you turn your encoder so fast that the timer is 0ms, the inc and dec of accelerating_counter_position for each pulse will be 101x per step.

This would be the means if you did not want to alter your existing rotary decoder's code directly.
You will need to use the CPU real time timer to make this work good.
You will also need to play with my factors to achieve the acceleration curve VS rotation speed you like.
Make sure that you filter out small switch debouncing noise when using a mechanical encoder in between steps.  Unlike the +/-1 inc & dec which self correct, mechanical noise now may mean and additional +100, -90 or similar now that the size of the bounce can be considered different velocity, therefor, a different amount of addition and subtraction may create a spurious reaction.


Good luck.
How do I time the amount between each increment?
I can also use this code that includes ISR:

Quote
#include <Rotary.h>

// Rotary encoder is wired with the common to ground and the two
// outputs to pins 2 and 3.
Rotary rotary = Rotary(2, 3);

// Counter that will be incremented or decremented by rotation.
volatile unsigned int counter = 0;
unsigned int x;
unsigned long timerotate;

void setup() {
  Serial.begin(57600);
  attachInterrupt(0, rotate, CHANGE);
  attachInterrupt(1, rotate, CHANGE);
}

void loop() {
      Serial.println(counter);
}

// rotate is called anytime the rotary inputs change state.
void rotate() {
  unsigned char result = rotary.process();
  if (result == DIR_CW && counter < 255) {
    counter++;
  } else if (result == DIR_CCW && counter > 0) {
    counter--;
  }
}

if that would make it easier.
the library is from here:
http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 7733
  • Country: ca
Since I've never used that library, I cannot give you a guaranteed working example, but, here is how I would modify your code so far.  This should work good if every 1/4 pulse is counted from your rotary encoder and there is no signal bounce.  This means every High and Low transition on both A&B inputs:

Quote
#include <Rotary.h>

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  #define MAX_TIME               5
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  #define ACCELL_FACTOR    2
// Rotary encoder is wired with the common to ground and the two
// outputs to pins 2 and 3.
Rotary rotary = Rotary(2, 3);

// Counter that will be incremented or decremented by rotation.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>  volatile int counter = 0;
unsigned int x;
unsigned long timerotate;

void setup() {
  Serial.begin(57600);
  attachInterrupt(0, rotate, CHANGE);
  attachInterrupt(1, rotate, CHANGE);
}

void loop() {
      Serial.println(counter);
}

// rotate is called anytime the rotary inputs change state.
void rotate() {
  unsigned char result = rotary.process();
  if (result == DIR_CW ) {
    speed = 1;
      if (timer_val < MAX_TIME) speed = (MAX_TIME - timer_val) * ACCELL_FACTOR;
    timer_val = 0;
    counter += speed;
    if (counter > 255) counter = 255;
  } else if (result == DIR_CCW) {
    speed = 1;
      if (timer_val < MAX_TIME) speed = (MAX_TIME - timer_val) * ACCELL_FACTOR;
    timer_val = 0;
    counter -= speed;
    if (counter < 0) counter = 0;
  }
}

if that would make it easier.
the library is from here:
http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html

Now, this is coded messy since it is not my time driven algorithm in an earlier post you thanked me for, it is interrupt driven so you will have to work out the clock is incremented,  I would recommend using a system timer which increments 10 times a second depending on your needs.  Also Don't forget you can adjust the constants MAX_TIME and ACCELL_FACTOR to get the acceleration curve you like.

If you setup (timer_val) properly yourself, pointing to an onchip timer which runs at the speed I recommended, with the settings I placed above, your volume should run at a step of 1 when turning it slower than 1 pulse every 0.5 seconds, smoothly ramping up to 10x per step as you rotate the encoder faster than 1/10th of a second per step.
« Last Edit: January 14, 2017, 06:45:31 pm by BrianHG »
 

Offline 0xPIT

  • Regular Contributor
  • *
  • Posts: 65
My Encoder Library does what you want, in a very intuitive way: The faster you turn, the faster you'll get increments.

Check my examples or other projects in my github on how to use the library.

https://github.com/0xpit/encoder
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf