Programing motor with encoder

Hello,

I have very little knowledge of coding all of my other projects I have done for work I've used stepper motors. But this time I opted for a geared dc motor with encoder because the size and dimensions work better for the machine I'm making. The problem is, I have never worked with encoded motors before and all of the tutorials I've watched are way more complex than what I'm looking for and just leaves me with little gained understanding of using them.

The purpose of the motor is to rotate an object a preset amount, pause so other actions can take place, then rotate again, pause, and so on.

But all tutorials I've seen add pots, follow sin waves, use the serial port, etc. I'm having a hard time weeding out all of the extra stuff and reducing the code to the bare bones of what I need.

Ideally I would spend the time to do more research and figure it out, but this machine is for work and I'm trying to continue moving forward with it and this is what is hanging me up.

I'd appreciate any help I can get.

Thank you!

The parts I'm using are:

  • Arduino Mega
  • L298N Motor Driver
  • 12V Geared DC Motor with Encoder 1:478 with 5736 Pulses per revolution

here is one of the tutorials I've gone through:
https://create.arduino.cc/projecthub/curiores/how-to-control-a-dc-motor-with-an-encoder-d1734c

Generally, people hook up the encoder signals and use an interrupt to read them. I'd start there, turn the motor on and count pulses, printing the count once a second or so. Your motor is 12V, hopefully the encoder is based on what you power it with separately - find out before you hook anything up.

1 Like

Did the tutorial work with your setup?

The sin() and int target = 250*sin(prevT/1e6);, etc..
part in the tutorial was extra stuff to update the target position to make the motor do something interesting for the demo.

You could combine that demo with the Blink Without Delay sketch and put 'target = 250;' and 'target = 0;' in with the toggling.

the tutorial seams to work the motor pivots back and forth but since it is geared it is hard to say that its working properly

but sometime the motor just randomly stops and then picks up again. almost like it gets caught up.

i just adjusted the code to

int target = 5000*sin(prevT/1e6);

instead of

int target = 250*sin(prevT/1e6);

but the motor doesn't seem to be moving any more. Am I missing something?

Well, prevT is in milliseconds since reboot, and prevT/1e6 is in seconds, so sin(prevT/1e6) oscillates between 1+/-1 every 2pi seconds, and 250 that means it should go +/-250 encoder counts every 6.3 seconds. Your 5000* means it should go +/- 5000 counts every 6.3 seconds.

I'd try moving slower if it doesn't seem to be working.

If you want to make it go half as fast and twice as far, try:

int target = 2 * 250*sin(0.5 * prevT/1e6);

It's so hard to tell if its actually changing speeds with it being geared. Is there a way to adjust it to get it to complete a full rotation?

So if it is 5736 pulses per complete revolution, then this might do it:

int target = 5736*sin(1.0* prevT/1e6);

One reason it could fail is if it can't physically turn the motor the 2478 revolutions in 2pi sec. That would be an average of 2478/2/pi60 = 9129 RPM. For testing I might aim for 91rpm at the motor with:

int target = 5736*sin(0.01* prevT/1e6);

... and expect a cycle will take 1002pi seconds. And then ramp up the speed to see how fast it can go.

I just implemented those changes and it it does make it turn farther but it doesn't operate properly it gets hung up a lot.

int target = 5736*sin(0.01* prevT/1e6);

changing to

int target = 5736*sin(0.05* prevT/1e6)

makes it rotate almost a complete rotation but the motor also gets hung up less

but changing the

int target = 5736*sin(0.01* prevT/1e6)

to

int target = 5736*sin(1.0* prevT/1e6)

doesn't make the motor spin faster or slower but makes the rotational distance smaller

To look at some actual code, I copied the code from the tutorial into Wokwi at sketch.ino - Wokwi ESP32, STM32, Arduino Simulator and here:

// Code copied from https://create.arduino.cc/projecthub/curiores/how-to-control-a-dc-motor-with-an-encoder-d1734c
// for discussion at https://forum.arduino.cc/t/programing-motor-with-encoder/962793/10
//
// DaveX added Serial.print(pwr*dir)
// In Wokwi, there's no functional motor or encoder, 
// so try the Serial Plotter icon to see what it is trying to do

#include <util/atomic.h> // For the ATOMIC_BLOCK macro

#define ENCA 2 // YELLOW
#define ENCB 3 // WHITE
#define PWM 5
#define IN2 6
#define IN1 7

volatile int posi = 0; // specify posi as volatile: https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/
long prevT = 0;
float eprev = 0;
float eintegral = 0;

void setup() {
  Serial.begin(9600);
  pinMode(ENCA,INPUT);
  pinMode(ENCB,INPUT);
  attachInterrupt(digitalPinToInterrupt(ENCA),readEncoder,RISING);
  
  pinMode(PWM,OUTPUT);
  pinMode(IN1,OUTPUT);
  pinMode(IN2,OUTPUT);
  
  Serial.println("target pos");
}

void loop() {

  // set target position
  //int target = 1200;
  int target = 250*sin(prevT/1e6);

  // PID constants
  float kp = 1;
  float kd = 0.025;
  float ki = 0.0;

  // time difference
  long currT = micros();
  float deltaT = ((float) (currT - prevT))/( 1.0e6 );
  prevT = currT;

  // Read the position in an atomic block to avoid a potential
  // misread if the interrupt coincides with this code running
  // see: https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/
  int pos = 0; 
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    pos = posi;
  }
  
  // error
  int e = pos - target;

  // derivative
  float dedt = (e-eprev)/(deltaT);

  // integral
  eintegral = eintegral + e*deltaT;

  // control signal
  float u = kp*e + kd*dedt + ki*eintegral;

  // motor power
  float pwr = fabs(u);
  if( pwr > 255 ){
    pwr = 255;
  }

  // motor direction
  int dir = 1;
  if(u<0){
    dir = -1;
  }

  // signal the motor
  setMotor(dir,pwr,PWM,IN1,IN2);


  // store previous error
  eprev = e;

  Serial.print(target);
  Serial.print(" ");
  Serial.print(pos);
  Serial.print(" ");
  Serial.print(pwr*dir);
  Serial.println();
}

void setMotor(int dir, int pwmVal, int pwm, int in1, int in2){
  analogWrite(pwm,pwmVal);
  if(dir == 1){
    digitalWrite(in1,HIGH);
    digitalWrite(in2,LOW);
  }
  else if(dir == -1){
    digitalWrite(in1,LOW);
    digitalWrite(in2,HIGH);
  }
  else{
    digitalWrite(in1,LOW);
    digitalWrite(in2,LOW);
  }  
}

void readEncoder(){
  int b = digitalRead(ENCB);
  if(b > 0){
    posi++;
  }
  else{
    posi--;
  }
}

Looking at the Wokwi simulation and it's serial plotter output, it looks like the code should be attempting to switch directions and vary the power/pwm according to time on about a 6 second cycle. Since I and the sim don't have your motor setup, we don't have functional motor response and encoder feedback, so it is difficult to say what is going on in your setup.

I think with the actual gearbox and the motor, it might not be able to keep up, and you get odd behavior.

In this case, I think the target is swinging back towards/past zero before the motor catches up with the target position.

I'd keep going slower until you get reliable, full motion.

If you have it connected to your computer while running, try the "Serial Plotter" and see how well the the encoder position tracks with the target.

Are you going to be reversing motor direction?

I'm no clear why you are following a tutorial with pid and a quadrature encoder. Are there pullups on the encoder outputs?

To get started I would just put one encoder output on an interrupt pin and count rising interrupts.


volatile unsigned long  count = 0;
unsigned long copyCount = 0;

unsigned long lastRead = 0;
unsigned long interval = 1000;//one second

void setup()
{
  Serial.begin(115200);
  Serial.println("start...");
  
  //interrupt on pin3
  pinMode(3,INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(3),isrCount,RISING);
}

void loop()
{
  if (millis() - lastRead >= interval) //read interrupt count every second
  {
    lastRead  += interval;
    // disable interrupts,make copy of count,reenable interrupts
    noInterrupts();
    copyCount = count;
    count = 0;
    interrupts();
    //use copyCount for all calulations and actions in loop
    Serial.println(copyCount);
  }
}

void isrCount()
{
  count++;
}
2 Likes

No the motor will only have to rotate in one direction, a fixed amount, i just want the amount to be consistent from each rotation.

I was following this because it was the most descriptive tutorial I could find, but yeas I realize its not really what I want, just thought if I did follow it I would eventually understand how they work, but that isn't the case.

Also I don't believe they have pull ups they are just direct leads to a bread board that I connected to Arduino mega, but since the pins aren't exactly the same as the tutorial cause they used an uno.


This is what the plotter looks like. I have no clue why its doing this. Maybe its the gears. But i thought the encoder is attached to the motor shaft and would read the output of the motor not the geared output shaft. Not only is it nowhere near the line its also not even switching directions when it should.

That plot is from your real setup, right?

It might be switching directions OK -- I think I can see a +1 pixel change at around 2650, and then a -1 pixel change at 2750 after the target crosses to below pos. pos it's just very slow compared to target.

Is the motor or gearshaft making a rotation in reasonable time?

Maybe set target to a constant and watch pos in the serial monitor and see how big it gets when you get a full rotation?

//int target = 5736*sin(0.05* prevT/1e6)

//int target = 5736*sin(0.05* prevT/1e6);
int target = 1000;
// After read pos from serial monitor to see there are only XXX steps/rev:
//int target = XXX*sin(0.05* prevT/1e6);




That makes your coding more simple.

Did you try the code I provided which counts one encoder output? Lets see how many counts you get per revolution. It's likely to be 5736/4. That should be enough resolution for you.

1 Like

I haven't just yet I'm working on that one right now, decided to do the plotter first because i already had the other code setup, ill post back when i have your code implemented.

I ran it with a target of 1000 and it rotates about a 1/4-1/3 a rotation then stops. serial plotter shows it has reached the target position as the motor stops so they seem to be working together just fine

But when it has reached the spot it still feels like the motor is vibrating and "running" even though its not actually moving

Maybe something is stuck in the gears at about a 1/4-1/3 a rotation and prevents it from rotating more.

What was pos when it stopped? Set target for 1/2 of pos when stopped, and see if it stops at 1/8-1/6th rev and stays still and quiet.

so when I run

  // set target position
  int target = 1000;
  //int target = 6000*sin(1.0* prevT/1e6);

I get about 1/4-1/3 rotations

  // set target position
  int target = 500;
  //int target = 6000*sin(1.0* prevT/1e6);

rotates about 1/8 rotation

  // set target position
  int target = 6000;
  //int target = 6000*sin(1.0* prevT/1e6);

just over one full rotation