DC Motor Wheel Encoder with Interrupts

Hey Makers,

I am trying to build a 4 wheel mobile robot. Right now, I am trying to set up the DC Motors with my encoders. Cheap ones but it should produce some metrics white spinning. I am having some issues with the language or the hardware I have placed. I dont know.

My issue is while reading the values from the encoders. Please find attached a youtube video and the code running.

What' s expected
I am expecting to have a LED turn on/off as the Encoder LED turn on/off. It seems that the onboard ARduino LED is not allied with the motor encoder LED. It feels like there is a delay here? Am I missing something? I have also used RISING & CHANGE.

#include <Wire.h>
#define ENCODER_PIN 2          // Left motor encoder.

int rotations = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("\t Ready");
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(ENCODER_PIN, INPUT);
  attachInterrupt( digitalPinToInterrupt(ENCODER_PIN), updateEncoder, RISING);
}

void updateEncoder() {
  rotations += 1;
  if( rotations % 2 == 0) {
      digitalWrite(LED_BUILTIN, LOW);
  }else {
      digitalWrite(LED_BUILTIN, HIGH);
  }
}

void loop() {
  
}

What have I tried
I have tried to Serial print the rotations variable and see what is happening. I was expecting an output like 1, 2, 3, 4, ... just like the LED in the motor encoder turn on. So If I see 4 times turn on I would expect 4. But the code procude a value of like 15. Something is messing things up.

I have also tried to minimize the whole light from the room.

I have also tried (as you can see in the video - my last test) to add a 330Ohm resistor. Nothing changed.

Youtube Link: [Arduino DC Motor Wheel Encoder with Interrupts - YouTube]
(Arduino DC Motor Wheel Encoder with Interrupts - YouTube)

Many thanks for reading my post.

  • Billy.

Any variable that is updated in an interrupt routine and read by the main code should be declared volatile. Otherwise, the C++ compiler can make incorrect assumptions about its value when it's attempting to optimise the machine code.

Please post a link to the specs of this encoder, ideally an internal schematic of the encoder so we can we what exactly makes its LED flash.

May be worth trying INPUT_PULLUP

No, I don't see a resistor. Did you post a link to the wrong video?

Thanks for the quick response. Actually there is a resistor at the top left corner connected with the green wire. Hard to see, sorry for that.

I tried INPUT_PULLUP and I have the save result.

Specifications:

Operating voltage: 3V~12VDC (recommended operating voltage of about 6 to 8V)
Maximum torque: 800gf cm min (3V)
No-load speed: 1:48 (3V time)
The load current: 70mA (250mA MAX) (3V)
This motor with EMC, anti-interference ability. The microcontroller without interference.
Size: 7x2.2x1.8cm(approx)

MANUFACTURER**HAITRONIC MPN**HS1460

Those look like the specifications of the motor, not the encoder.

I see a bare wire stuck in one of the Uno pin sockets. Is that the leg of the resistor? That probably won't make a reliable contact. Also sounds like you are using it as a series resistor, in series with an Arduino input pin? That won't do any good. Arduino input pins have an input impedance (resistance) of many megaOhms. An extra 330 won't make any difference. Why did you add the resistor?

Please post a schematic showing how everything is wired. Use pencil and paper if you are not familiar with any schematic drawing apps.

I see. I did my best.

As I said, I only need a simple "project" that allow me to give as input 21cm and see the wheel spin for a distance of 21cm. Which is actually a full rotation of the wheel encoder since I have 20 holes.

Please post a link to the encoder.

So If I see 4 times turn on I would expect 4. But the code procude a value of like 15.

It may be that the encoder output "bounces", that is goes through several transitions as the opening appears. You have to use a delay or other means to eliminate the problem. Look up "arduino switch bounce" for discussions.

Hi,

Do you need to declare the rotation variable as "volatile" so the compiler doesn't get rid of it?

I just watched the dronebot workshop episode where he built a car with rotation tracking:

Also, I assume you're using some form of h-bridge for motor control?

Regards

Hello, I came back with more information.

I think it is possible, so I need to add a delay but how much? I dont want to make it loose holes. You know what I mean?

Description: HC-020K speed measuring sensor is a wide voltage, high resolution, short response time, and the switch output speed measurement module. It can test motor's rotational speed with black encoder (measured spec. is related to the encoder, the inner diameter of D type encoder that provided is 4mm, can be used for motor output shaft w/ 4mm diameter, which is TT motor we matched, yellow shell and white axis).

Features:

Module Working Voltage: 4.5-5.5V
Launch Tube Pressure Drop: Vf=1.6V
Launch Tube Current: If<20mA
Signal output: A, B two lines; TT power level;
Resolution: 0.01mm
Measurement frequency: 100KHz
Disc diameter: 24mm
Inner Disc Diameter: 4mm
Encoder resolution: 20 lines
Speed measuring sensor configuration: measure line 1 motor speed
Application: For experiment

That circuit is a "Schmidt trigger" which has hysteresis, so it should not give multiple transitions.

But you can check for that by slowly rotating the wheel past one slot in the encoder disk, and see if you get multiple counts.

I tried use my hand. It seems that it throws many outputs. I mean, I was expecting doing this slowly and I am getting

1
1
1
0
1
0
0
0
1
1
1
0
....

Which means that I have to write some software and clear this out. Which I did. I am throwing away all 1 after the first occurance of 1 until I find a 0. Same story for 0.

I will try now to add a delay with 100ms and see if things do better.

Is there a chance that the speed of the interrupts outpaces the serial output? as they say to avoid using serial print in an interrupt. Which will make your print statements seem incorrect.

Can you add the results to a volatile string or array, then print that on occasion? Just to prove a theory.
It'll be awkward to empty, so you would need to implement some form of "first-in-first-out" management if you wanted to use it in the longer term.

Please post the code you are currently using, in your next post.

You cannot use delay() or serial data transmission in an interrupt, and there are several other rules you need to obey.

Let me summarize.

Do I have to

digitalRead( L_ENC ); 

in my routine that is triggered in each interrupt? If you ask me, I believe no. I saw that from someone but this is the job of the interrupt right? Once RISING, I need to increase the counter, nothing more. Need a verification on this.

If you agree, I have the following routine. But delay must not be there right? How can I handle it?

void updateLMHRot() {
  rotations++;    // Holes counter.
  delay(25);
}

Full code:


#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"

#define TRIG_PIN 6        // Ultrasonic distance sensor trig pin attached to.
#define ECHO_PIN 7        // Ultrasonic distance sensor echo pin attached to.
#define MAX_DISTANCE 400  // Maximum distance we want to measure (in centimeters).
#define L_ENC 18          // Left motor encoder.
#define R_ENC 19          // Right motor encoder.

#define WHEEL_DIAMETER 20  // cm per rotation a wheel does.
#define WHEEL_HOLES 21     // Number of holes a wheel encoder has.
#define DES_DISTANCE 30    // Desired distance in cm.

const boolean ENABLE_TEST_RUN = false;

// Initialize DC Motors
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *lMotor = AFMS.getMotor(1);
Adafruit_DCMotor *blMotor = AFMS.getMotor(2);
Adafruit_DCMotor *brMotor = AFMS.getMotor(3);
Adafruit_DCMotor *rMotor = AFMS.getMotor(4);

volatile int rotations = 0;

void moveForward(int vSpeed) {
  lMotor->setSpeed(vSpeed);
  rMotor->setSpeed(vSpeed);
  blMotor->setSpeed(vSpeed);
  brMotor->setSpeed(vSpeed);

  blMotor->run(FORWARD);
  brMotor->run(FORWARD);
  lMotor->run(FORWARD);
  rMotor->run(FORWARD);
}

void stopMoving() {
  lMotor->run(RELEASE);
  rMotor->run(RELEASE);
  blMotor->run(RELEASE);
  brMotor->run(RELEASE);
}

void setup() {
  Serial.begin(9600);
  AFMS.begin();
  pinMode(L_ENC, INPUT);
  attachInterrupt( digitalPinToInterrupt(L_ENC), updateLMHRot, RISING);
}

void updateLMHRot() {
  rotations++;
  delay( 10 );
}

void loop() {
  if( rotations >= 75) {
    stopMoving();
  }else {
    moveForward(100);
  }
}

I finaly have with a small error a full rotation when the rotations variable is ~75.

This is not allowed in an interrupt, because delay() depends on interrupts, which are turned off while the interrupt service routine is executing.

In this case, you are lucky that with AVR-based Arduinos, the delay value is simply ignored, so the function call does nothing. Most often when you make this sort of mistake, the processor just hangs.

Another mistake is to access the shared variable "rotations" in the main loop, without protecting it from corruption by the interrupt routine. The way to fix this is as follows:

noInterrupts(); //turn off interrupts
int rotations_copy = rotations; //copy the variable contents
interrupts(); //back on
Serial.println(rotations_copy);  //print or otherwise use the copy.

If your encoder is giving multiple counts for each slot in the encoder wheel, the only easy fix is to buy a better encoder.

I would highly recommend the dronebot workshop episode on this same topic, here is the website with the example Arduino code around using interrupts to measure wheel rotation.

He goes over the reasons to keep interrupts very quick and how/why to disable interrupts as recommended by @jremington

I took a look at the DroneBot Workshop example linked above, and with respect to interrupts, it sets a terrible example.

The following breaks all the rules, and all the code in this interrupt service routine should be in the main loop. And, it won't even keep good time!

 
// TimerOne ISR
void ISR_timerone()
{
  Timer1.detachInterrupt();  // Stop the timer
  Serial.print("Motor Speed 1: "); 
  float rotation1 = (counter1 / diskslots) * 60.00;  // calculate RPM for Motor 1
  Serial.print(rotation1);  
  Serial.print(" RPM - "); 
  counter1 = 0;  //  reset counter to zero
  Serial.print("Motor Speed 2: "); 
  float rotation2 = (counter2 / diskslots) * 60.00;  // calculate RPM for Motor 2
  Serial.print(rotation2);  
  Serial.println(" RPM"); 
  counter2 = 0;  //  reset counter to zero
  Timer1.attachInterrupt( ISR_timerone );  // Enable the timer
}

The interrupt service routine should set a flag that the timer has elapsed and exit, e.g.

volatile byte Timer1_elapsed = 0;
...
 
// TimerOne ISR
void ISR_timerone()
{
Timer1_elapsed = 1;
}

The main loop should detect the set flag, do all the calculations and printing and reset the flag.

Good point.

I don't know how the timerone library is written; he's basically using it as a scheduler to trigger a function, and detaching/reattaching the interrupt so as to not derail it. However your suggestion keeps the timer super-light, just like the other two interrupts. :+1:

However I'm not going to question most people's favorite uncle of Arduino :wink: he's many-shades of excellent. :clap: