Skip to main content
3 of 4
added 1 character in body
Ben A
  • 10.8k
  • 5
  • 38
  • 103

It's great that you have a library that works for you, but now you should think of redesigning it. I see that class DDBot is implementing the low-level functionality (controlling pins and PWM values), and class ForwardDDBot provides a more high-level interface that also takes care of scaling values and smoothly changing motor speeds. If that is the case, then I see some issues:

Keep DDBot as simple as possible

There are so many functions in DDBot, but do you really need them all? If this is supposed to be for low-level control of the motors, I think you only need one function that sets the speed of the left and right motor. By taking the parameters as signed integers, you avoid having to pass the directions:

void DDBot::setSpeed(int leftSpeed, int rightSpeed) {
    digitalWrite(directionPins[0], left > 0);
    digitalWrite(directionPins[1], left < 0);
    digitalWrite(directionPins[2], right > 0);
    digitalWrite(directionPins[3], right < 0);

    analogWrite(PWMPins[0], abs(leftSpeed));
    analogWrite(PWMPins[1], abs(rightSpeed));
}

You can do any move you want with just this function.

Think about physical units

If I would ask you "how fast can your robot go?", would your answer be "255"? No, you would say something like "X meters per second". For a higher level interface, it would be nice to provide such a physical unit, and have your library convert it to a PWM setting. The question then is, what does the PWM value control? Is it the rotation speed of the motor? In that case, you could consider using "revolutions per second", or if you know the diameter of the wheels, "meters per second" as the unit for parameters passed to the high level interface.

It could also be that the PWM value doesn't control the speed, but rather the torque. Torque is measured in newton meters, and given the mass of your robot, you could calculate the acceleration a given torque would provide. Once the robot reaches a certain speed, the torque is canceled out by friction, and the robot will then stay at that speed. This could also be measured or calculated to some degree, so that you could provide an interface where you provide speed in meters per second, and it will then convert that to the right PWM value to reach that speed.

There is no feedback control

You mention "feedback control" in your comments, however what you have implemented does not rely on feedback at all, instead it's an open-loop controller to smooth the transition from one speed to another. This will indeed help smooth the motion of the robot, but the way you implemented it is very naive.

First, consider that the smoothing depends entirely on how often write() is called. But what should the delay between calls to write() be? If you call it too often, the robot will move jerky again. If you don't call it often enough, the robot will take a long time to reach the desired speed. You could actually check in write() how long it was since the last time it was called, and take the actual delay into account. That would remove the responsibility of calling write() at exactly the right interval from the caller into your library.

But then consider that the first call to write(), you take a large step, and subsequent calls make smaller and smaller steps. So it can still make a jerky movement when you make a large speed change. A smoother way to change speeds is to just add or subtract one from the actual PWM value in each call to write(), until you reach the desired value.

Note that jerk is a mathematically well-defined concept. Since it is the derivative of the acceleration, it's easy to see that you should keep changes in acceleration as low as possible to minimize jerk.

Be careful when doing math with integers

Consider this line of code:

leftActualPWM = (leftTargetPWM + leftActualPWM) / 2;

Since both leftTargetPWM and leftActualPWM are uint8_t values, the result will also be an uint8_t value. If the sum of the two values would be larger than 255, the result will overflow and wrap. Only after that will it divide by 2. A safer way would be to divide each value by two first:

leftActualPWM = leftTargetPWM / 2 + leftActualPWM / 2;

But that way you will lose one bit of precision. You can also cast one of the values to uint16_t, so the result will be an uint16_t value:

leftActualPWM = (static_cast<uint16_t>(leftTargetPWM) + leftActualPWM) / 2;

If the CPU in your Arduino has hardware support for floating point operations, I would rather store most variables as float, and only convert to uint8_t PWM values right before calling analogWrite().

G. Sliepen
  • 69.3k
  • 3
  • 75
  • 180