calculating velocity with a dc motor encoder

Hello, i am making a project where i want to calculate the velocity and rotation direction of a arduino motor.
This is the code so far:

// Rotary Encoder Inputs
#define CLK 2
#define DT 3

int enableA = 9;
int in1= 6;
int in2= 5;

int counter = 0;
 int currentStateCLK;
 int lastStateCLK;
 String currentDir ="";

void setup() {
	
  pinMode(enableA, OUTPUT);
  pinMode (in1, OUTPUT);
  pinMode (in2, OUTPUT);
  
// Set encoder pins as inputs
 pinMode(CLK,INPUT_PULLUP);
 pinMode(DT,INPUT_PULLUP);

// Setup Serial Monitor
 Serial.begin(9600);

// Read the initial state of CLK
 lastStateCLK = digitalRead(CLK);
	
// Call updateEncoder() when any high/low changed seen  on interrupt 0 (pin 2), or interrupt 1 (pin 3)
attachInterrupt(0, updateEncoder, CHANGE);
attachInterrupt(1, updateEncoder, CHANGE);
}

void loop() {
	digitalWrite(enableA, 255);
  	analogWrite(in1, LOW);
    analogWrite(in2, HIGH);
  }


void updateEncoder(){
	// Read the current state of CLK
	currentStateCLK = digitalRead(CLK);

	// If last and current state of CLK are different, then pulse occurred
	// React to only 1 state change to avoid double count
	if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){

		// If the DT state is different than the CLK state then
		// the encoder is rotating CCW so decrement
		if (digitalRead(DT) != currentStateCLK) {
               counter --;
				currentDir ="CCW";
             
		} else {
			// Encoder is rotating CW so increment
			counter ++;
			currentDir ="CW";
		}

		Serial.print("Direction: ");
		Serial.print(currentDir);
		Serial.print(" | Counter: ");
		Serial.println(counter);
	}

	// Remember last CLK state
	lastStateCLK = currentStateCLK;
}

i am simulating this project on tinkercad: https://www.tinkercad.com/things/kbaWLn0y02Y-copy-of-dc-motor-encoder/editel
the motor isn`t spinning fast but i think that this is a tinkercad problem?
however i have managed to get the counter and the direction working but the next problem is how i can calculate the velocity based on the pulses from the encoder.

any suggestions?

The circumference of the wheel that is connected to the shaft has a distance. Lets say 1 million meters. So when the wheel has went around once the wheel traveled one million meters. If the wheel rotates once an hour then the rate of travel is 1 million meters per hour. If the wheel spins around 2 times per hour then the speed is 2 million meters per hours. Hope that helps.

if life was that easy…

i know how to calculate the velocity the problem is programming the velocity.
My counter counts each pulse so i have to figure out what the time is between the pulses and how many pulses it takes for one full rotation. when i know these numbers i know the time it takes for one rotation and can multiply that by the cicumference of the wheel connected to the shaft.

Pulse, store millis() or micros(), pulse, store millis() or micros(), Currentmillis/micros MINUS Previousmillis/micros, get difference in millis/micros gives time of travel, length of travel is wheel roundy distance. How much more complicated can it get?

There was a topic discussing an anemometer some time back. That was calculating wind speed using pulses against time. If you can find it it may help?

There are a number of problems with your code as it currently stands. Although it may work in a simulator, it will probably fail on a real Arduino:

  1. Avoid Strings, especially on AVR based Arduinos, as Strings cause your program to crash.

  2. NEVER do serial I/O from within interrupt routines. This goes in the main code (the loop function).

		Serial.print("Direction: ");
		Serial.print(currentDir);
		Serial.print(" | Counter: ");
		Serial.println(counter);
  1. Variables shared between the main code and interrupt routines must be declared volatile, or they may never be updated.
volatile int counter = 0;
  1. Multibyte variables shared with interrupts routines must be protected against corruption, if an interrupt happens while the main code is accessing the variable. Do this as follows:
// within the loop function:
noInterrupts(); //turn off interrupts
int count_copy = counter; //make a copy
interrupts(); //turn them back on
... (use the copy in subsequent code)

the problem is programming the velocity.

You set the motor PWM value to set the velocity. Most people use a PID loop to adjust the PWM value to match a desired velocity.

thank you very much for pointing out some of the coding problems. i tried to fix number 1,2 and 3, this is the result:

// Rotary Encoder Inputs
#define CLK 2
#define DT 3

int enableA = 9;
int in1= 6;
int in2= 5;
volatile bool flagInterrupt; 
volatile int counter = 0;
int currentStateCLK;
int lastStateCLK;
String currentDir ="";

void setup() {
	
  pinMode(enableA, OUTPUT);
  pinMode (in1, OUTPUT);
  pinMode (in2, OUTPUT);
  
// Set encoder pins as inputs
  pinMode(CLK,INPUT_PULLUP);
  pinMode(DT,INPUT_PULLUP);

// Setup Serial Monitor
  Serial.begin(9600);

// Read the initial state of CLK
  lastStateCLK = digitalRead(CLK);
	
// Call updateEncoder() when any high/low changed seen
// on interrupt 0 (pin 2), or interrupt 1 (pin 3)
  attachInterrupt(0, updateEncoder, CHANGE);
  attachInterrupt(1, updateEncoder, CHANGE);
}

void loop() {
  digitalWrite(enableA, 255);
  analogWrite(in1, LOW);
  analogWrite(in2, HIGH);

  noInterrupts(); 				//turn off interrupts
  int count_copy = counter; 			//make a copy
  bool flag_copy = flagInterrupt;
  interrupts(); 				//turn them back on

  if(flag_copy = true){
    Serial.print("Direction: CW");
  }
  	else{
    Serial.print("Direction: CCW");
  	}

  //Serial.print("Direction: ");
  //Serial.print(flag_copy);
  Serial.print(" | Counter: ");
  Serial.println(count_copy);
}

void updateEncoder(){
  currentStateCLK = digitalRead(CLK);	// Read the current state of CLK
  
	if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){// If last and current state of CLK are different, then pulse occurre // React to only 1 state change to avoid double count
		
      if (digitalRead(DT) != currentStateCLK) {	// If the DT state is different than the CLK state then the encoder is rotating CCW so decrement
          counter --;
          flagInterrupt = false;	//currentDir ="CCW";
             
		} else {
		  counter ++;				// Encoder is rotating CW so increment
         	  flagInterrupt = true;	                //currentDir ="CW"
		}
	}

	// Remember last CLK state
	lastStateCLK = currentStateCLK;
}

I made a volatile boolean flagInterrupt, set true if the motor is rotating CW and false if the motor is rotating CCW in the ISR. (i didnt know if this was needed just like the count_copy so i did it anyway) i made a copy of the flag just like the count_copy and used a simple if/else if statement to print the direction. however the result is always CW.

if i let the direction print the direct results, it works and the output is a 0 or 1. using these //… lines, instead of the if/elsif code…

 //Serial.print("Direction: ");
  //Serial.print(flag_copy);
  Serial.print(" | Counter: ");
  Serial.println(count_copy);

You set the motor PWM value to set the velocity. Most people use a PID loop to adjust the PWM value to match a desired velocity.

that is correct. i am trying to figure out how a motor + encoder is working and how i can calculate the velocity and direction of the motor with just a steady PWM signal. when i understand this the next step is to implement a PID loop and potmeter so i can adjust the desired velocity and let the PID controller keep that velocity.

Oops! "==" required for comparison.

  if(flag_copy = true)

alright this part seems to work fine on my encoder. this is the code:

// Rotary Encoder Inputs
#define CLK 2
#define DT 3

int enableA = 9;
int in1= 6;
int in2= 5;
volatile bool flagInterrupt; 
volatile int counter = 0;
int currentStateCLK;
int lastStateCLK;


void setup() {
  
  pinMode(enableA, OUTPUT);
  pinMode (in1, OUTPUT);
  pinMode (in2, OUTPUT);
  
// Set encoder pins as inputs
  pinMode(CLK,INPUT_PULLUP);
  pinMode(DT,INPUT_PULLUP);

// Setup Serial Monitor
  Serial.begin(9600);

// Read the initial state of CLK
  lastStateCLK = digitalRead(CLK);
  
// Call updateEncoder() when any high/low changed seen on interrupt 0 (pin 2), or interrupt 1 (pin 3)
  attachInterrupt(0, updateEncoder, CHANGE);
  attachInterrupt(1, updateEncoder, CHANGE);
  
  digitalWrite(enableA, 255);
  analogWrite(in1, HIGH);
  analogWrite(in2, LOW);

}

void loop() {
  noInterrupts();                     //turn off interrupts 
  int count_copy = counter;           //make a copy
  bool flag_copy = flagInterrupt;
  interrupts();                       //turn them back on  

  if(flag_copy == true){
    Serial.print("Direction: CW");
  }
    else{
    Serial.print("Direction: CCW");
    }
 
 //Serial.print("Direction: ");
 //Serial.print(flag_copy);
 
 Serial.print(" | Counter: ");
 Serial.println(count_copy);
}

void updateEncoder(){
  currentStateCLK = digitalRead(CLK);                                         // Read the current state of CLK
  if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){              // If last and current state of CLK are different, then pulse occurre // React to only 1 state change to avoid double count
      if (digitalRead(DT) != currentStateCLK) {                               // If the DT state is different than the CLK state then the encoder is rotating CCW so decrement
          counter --;
          flagInterrupt = false;  //currentDir ="CCW";     
      } 
          else {
              counter ++;       // Encoder is rotating CW so increment
              flagInterrupt = true; //currentDir ="CW"
          }
  }

  lastStateCLK = currentStateCLK;                                             // Remember last CLK state
}

so now i want to measure the RPM`s.

Like Idahowalker said:

Pulse, store millis() or micros(), pulse, store millis() or micros(), Currentmillis/micros MINUS Previousmillis/micros, get difference in millis/micros gives time of travel, length of travel is wheel roundy distance. How much more complicated can it get?

so where do i store these times? in the ISR of in the loop ?

void updateEncoder(){ 
cTime = micros(); // stores the current time every pulse.
currentStateCLK = digitalRead(CLK); // Read the current state of CLK
  
  if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){// If last and current state of CLK are different, then pulse occurre // React to only 1 state change to avoid double count
    
      if (digitalRead(DT) != currentStateCLK) { // If the DT state is different than the CLK state then the encoder is rotating CCW so decrement
          counter --;
          flagInterrupt = false;  //currentDir ="CCW";
             
    } else {
      counter ++;       // Encoder is rotating CW so increment
          flagInterrupt = true; //currentDir ="CW"
    }
  }
 dTime = cTime - lTime;   // dTime = current time - last time
 lTime = cTime; 
  // Remember last CLK state
  lastStateCLK = currentStateCLK;
}

so i have the time between pulses. now in the main loop i can make the rpm code:

void loop() {
  
  noInterrupts();           //turn off interrupts
  int count_copy = counter;       //make a copy
  bool flag_copy = flagInterrupt;
  int dTime_copy = dTime
  interrupts();             //turn them back on

  if(flag_copy == true){
    Serial.print("Direction: CW");
  }
    else{
    Serial.print("Direction: CCW");
    }
 
 //Serial.print("Direction: ");
 //Serial.print(flag_copy);
 Int rpm = ......?
 Serial.print(" | rpm: ", 3);
 Serial.println(rpm);
}

is this the correct way to use the time between pulses? and how can i calculate the RPM. i was thinking of this:
(dTime * ppr) / 1,000,000.0 = the time of 1 full rotation in seconds. ( my PPR = 100 ).
1 second / 1 full rotation = rotations per second.
rotations per second * 60 = RPM.

how about the calculations?

For your wheel, 1 RPM is 100 pulses captured during 1 minute.

So the general formula for RPM is

(number of pulses captured per minute)/100

or

60*(number of pulses captured per second)/100

The capture time should be made available to the main loop, using a volatile variable shared with the ISR.

that`s probably a better solution than my calculations. Here is what i made of it:

void loop() {
  noInterrupts();                       //turn off interrupts
  int count_copy = counterRPM;          //make a copy
  bool flag_copy = flagInterrupt;
  interrupts();                         //turn them back on

  if(flag_copy == true){
    Serial.print("Direction: CW");
  }
    else{
    Serial.print("Direction: CCW");
    }

  rps = abs(count_copy / 100);
  rpm = (rps * 60);
  float velocity = (rps * 6.28 * 0.05 ); // velocity in m/s
   
    Serial.print(" | rps: ");
    Serial.print(rps);
    Serial.print(" | rpm: ");
    Serial.print(rpm);
    Serial.print(" | velocity: ");
    Serial.print(velocity);
    Serial.println(" m/s");
  
}

void updateEncoder(){
  currentStateCLK = digitalRead(CLK); // Read the current state of CLK
  if (currentStateCLK != lastStateCLK  && currentStateCLK == 1){// If last and current state of CLK are different, then pulse occurre // React to only 1 state change to avoid double count
    
      if (digitalRead(DT) != currentStateCLK) { // If the DT state is different than the CLK state then the encoder is rotating CCW so decrement
          counter --;
          flagInterrupt = false;  //currentDir ="CCW";
             
    } else {
      counter ++;       // Encoder is rotating CW so increment
          flagInterrupt = true; //currentDir ="CW"
    }
  }
    cTime = millis();               // stores the time in millisecond
    if(cTime - lTime >= 1000) {     // checks if 1 second has passed
    lTime = cTime;                  // make the ltime the current time for next iteration 
    counterRPM = counter;           // make a new variable with the counted pulses
    counter = 0;                    // reset counter
    }

// Remember last CLK state
  lastStateCLK = currentStateCLK;
}

so in the encoder code the counterRPM gets a update every second. but in the mainloop the counter_copy gets a update every time the program runs. how can i make the counter_copy only getting updated when the second has passed? if i write someting like this would that work (i can`t test it right now):

 void loop() {
  noInterrupts();                       //turn off interrupts
  int count_copy = counterRPM;          //make a copy
  bool flag_copy = flagInterrupt;
  interrupts();                         //turn them back on

  if(flag_copy == true){
    Serial.print("Direction: CW");
  }
    else{
    Serial.print("Direction: CCW");
    }

 if(count_copy != dCounter){
  rps = abs(count_copy / 100);
  rpm =(rps * 60);
  float velocity = (rps * 6.28 * 0.05 ); // velocity in m/s
   
    Serial.print(" | rps: ");
    Serial.print(rps);
    Serial.print(" | rpm: ");
    Serial.print(rpm);
    Serial.print(" | velocity: ");
    Serial.print(velocity);
    Serial.println(" m/s");
   dCounter = count_copy;

  }
}

This is the wrong way around.

  rps = abs(count_copy / 100);
  rpm = (rps * 60);

To preserve accuracy with integer calculations, multiply before divide as follows:

rpm = 60*abs(count_copy)/100;

Note: n/d = 0, if n<d

Sorry i didn’t mention that the variable of rpm and rps are float so that, for example, n=10 and d= 100 the result could have a decimal result of 0.1 instead of 0. Only the count_copy is an integer.

So this is not the case in my program?

This expression evaluates to an integer, so the float value will also be an integer.
If count_copy is less than 100, rps will be zero.

rps = abs(count_copy / 100);

If you want a floating point result with valid decimal fractions, either use casting or an explicit floating point constant.
Either

 rps = ((float) abs(count_copy)) / 100;

or

 rps = abs(count_copy) / 100.0;