DC Motor speed read via encoder - Arduino Nano

Hello guys

I am trying to read the speed of my 12v DC motor, 374ppr and max speed of 360rpm; through its encoder with the 16MHz crystal Arduino Nano. The driver is L298N.

Basically, my idea is to read the number of pulses from the encoder's channel A in a set sampling time of 0.02s. This time period is determined using Timer1 interrupt.

My result turns out to have the right result (averagely) but with significant errors, about 7-10% when ran at any speed.
Hope you guys can help me figure out what's going wrong and maybe negate the error to less than 2%, preferably.

Any help is greatly appreciated

volatile unsigned int count = 0; //pulse counting variable
volatile unsigned int temp = 0;  //use to store count value before reset
float velo = 0;

void setup()
{
  pinMode(2,INPUT_PULLUP); // Encoder channel A
  pinMode(3,INPUT_PULLUP); // Encoder channel B
  pinMode(7,OUTPUT);        // IN1 on L298N driver
  pinMode(8,OUTPUT);        //IN2 on L298N
  pinMode(5,OUTPUT);        //PWM output pin

  
  Serial.begin(57600);
  cli(); 
  //Timer1 setup for 0.01s increment
  TCCR1A = 0x00;
  TCCR1B = 0x00;
  TIMSK1 = 0x00;

  TCCR1B |=  (1 << CS12);
  TCNT1 = 0;
  OCR1A = 1249;
  TIMSK1 |= (1 << OCIE1A);
//    
  sei();
  //Initiates interrupt on Channel A pin
  attachInterrupt(0, pulnum0, RISING);
}
 
void loop(){
  digitalWrite(7, HIGH);
  digitalWrite(8, LOW);
  analogWrite(5, 255);

  velo = RPM(temp);
  Serial.println(velo); //print veloctity value to serial bus
}

ISR (TIMER1_COMPA_vect){
  cli();
  TCNT1 = 0;
  temp = count; //save value to temp before reset
  count = 0;    //reset count value
  sei();
}

//ISR for Interrupt on Pin D2
void pulnum0(){
  count++; 
}
//Velocity calculator
float RPM(double delta){
  float a =  delta * 3000 / (374 * 1);
  return a;
}

sketch_nov07b.ino (1.21 KB)

HuyNguyen1298:
my idea is to read the number of pulses from the encoder's channel A in a set sampling time of 0.02s.

Might it not be better to count a set number of pulses in a variable time?

Your way, the 0.02 might expire juuuust before a pulse. If the number of pulses read in such a short time is small, then effectively missing one pulse could be significant. If you miss say the 3rd pulse of 3, you would be saying there are 2 pulses / 0.02 seconds (100/s) not 3 pulses / 0.021 seconds (143/s), kind of thing.

(It's less significant of course if the number of pulses in the period is large.)

sayHovis:
Might it not be better to count a set number of pulses in a variable time?

That would also be my approach. When the pulse count gets to X save the value of micros() and set a flag variable so the main program knows there is a new value. Something like this pseudo code

void mySpeedISR() {
   count++
   if (count >= threshold) {
      isrMicros = micros();
      newIsrValue = true;
      count = 0;
   }
}

then your code in loop() can do something like this

if (newIsrValue == true) {
   prevMicrosVal = microsVal;
   noInterrupts();
      microsVal  = isrMicros;
      newIsrValue = false;
   interrupts();
   // code to update the speed calculations
}

...R

Thanks for your ideas, guys

As i tried the new method with the following code:

volatile unsigned int count = 0; //pulse counting variable
volatile unsigned int temp = 0;  //use to store count value before reset
volatile float velo = 0;
volatile unsigned long newtime=0;
volatile unsigned long oldtime=0;
volatile unsigned long isrMicros=0;
int check=0;


void setup()
{
  pinMode(2,INPUT_PULLUP); // Encoder channel A
  pinMode(3,INPUT_PULLUP); // Encoder channel B
  pinMode(7,OUTPUT);        // IN1 on L298N driver
  pinMode(8,OUTPUT);        //IN2 on L298N
  pinMode(5,OUTPUT);        //PWM output pin
  analogWrite(5,255);
  
  Serial.begin(9600);
 
  //Initiates interrupt on Channel A pin
  attachInterrupt(0, pulnum0, RISING);
}
 
void loop(){
  digitalWrite(7, HIGH);
  digitalWrite(8, LOW);
  if(check==1)
  {
    oldtime=newtime;
    noInterrupts();
    newtime=isrMicros;
    check=0;
    interrupts();
    velo=10*60000*1000/((newtime-oldtime)*374);
    
  }
  Serial.println(velo); //print veloctity value to serial bus
}


//ISR for Interrupt on Pin D2
void pulnum0()
{
  count++; 
  if(count==11)
  {
    isrMicros=micros();
    check=1;
    count=0;
  }
}

//Velocity calculator
//float RPM(double delta){
//  float a =  delta * 3000 / (374 * 2);
//  return a;
//}

The velocity returned at duty cycle 100% is no longer around 300rpm but now 260rpm
And more surprisingly, i have the same velocity at duty cycle 50% and 20% is pretty much the same at 180rpm (It can't be right)

Did anything else in my code go wrong ? Besides the logic, cause i double-checked it with the pseudo code you provide already.

Thank you
Huy Nguyen

In your Original Post you say there are 374 pulses per revolution. If this was my project I think I would count to 187 or 374 before noting the time - i.e. a half or a full revolution. Using a very short count risks being confused by interference from other Arduino interrupts.

Also micros() increments in steps of 4.

You really need an independent measurement of the speed to verify your program.

...R

You need to have a critical section around the code that reads the variables in the main code. The ISR is already
protected from other interrupts happening.

int read_temp()
{
  noInterrupts() ;
  unsigned int val = temp ;  // no interrupts can happen between reading the high and low byte of the volatile variable temp
  interrupts() ;
  return val;
}