smooth DC motor control with millis

So the task is to control 12v DC motor.

With first button, you can add up motor speed by certain speed step.

With second button, you can slow down motors speed by certain speed step.

With third button you cant speed up motor to its max, but problem is there that i must be done smoothly. For example i press third button and in motor starts to spin faster and faster till it reaches max speed. But now when i press this button motor jumps to it max, and i don't know why. There is no smoothness.

I cant use delay, must be done by millis

[code]
int speed = 0;
int motor = 5,
fasterbutton = 7, slowerrbutton = 4, tillmaxbutton = 2;
unsigned long previousMillis = 0;
const long interval = 100;
const long smoothinterval = 5000;
boolean faster, slower, tillend;
int begginingspeed;
const int step = 3;
const int maxspeed = 255;

void setup() {
pinMode(2, INPUT_PULLUP);
pinMode(4, INPUT_PULLUP);
pinMode(5, OUTPUT);
pinMode(7, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {

//button Nr 7 start DC motor and festens up
if (faster == !HIGH && begginingspeed < maxspeed) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
begginingspeed += step;
if (faster == !HIGH && speed < 1 ) {
Serial.print('\n');
Serial.print("MOTOR ON" );
}
analogWrite(motor, begginingspeed);
Serial.print('\n');
Serial.print("Speed = " );
speed = begginingspeed * 100 / maxspeed;
Serial.print(speed, DEC);
previousMillis = currentMillis;
}
}

//button Nr 4 slows down  DC motor
else if (slower == !HIGH && begginingspeed > 0) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
begginingspeed -= step;
analogWrite(motor, begginingspeed);
Serial.print('\n');
Serial.print("Speed = " );
speed = begginingspeed * 100 / maxspeed;
Serial.print(speed, DEC);
if (slower == !HIGH && speed < 1 ) {
Serial.print('\n');
Serial.print("MOTOR OFF" );
}
previousMillis = currentMillis;
}
}

//button Nr 2 for smooth fastens up DC motor till max
if (tillend == !HIGH && begginingspeed > 0) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis <= interval) {
for (begginingspeed = begginingspeed; begginingspeed <= 255; begginingspeed += step)
analogWrite(motor, begginingspeed);
}
previousMillis = currentMillis;
}

}
[/code] • you need more state, 1 more boolean, say, is_accelerating. You set this true when the button is pressed, and set it false when speed is max. It basically replaces faster == !HIGH so you don't have to hold down the button. Dec 18 '20 at 18:12
• @dandavis in my code i don't need to hold button to reach max speed. Dec 18 '20 at 18:17
• the for loop in if (tillend == !HIGH runs instantly. kill the for loop, then hold the tillend button to ramp up smoothly. then make my mod so you don't have to hold the button. Dec 18 '20 at 18:23
• @dandavis you need more state, 1 more boolean, say, is_accelerating. You set this true when the button is pressed, and set it false when speed is max. It basically replaces faster == !HIGH so you don't have to hold down the button. SO could you explain me this part, i din't get it how to declare all of this. Dec 18 '20 at 18:52
• @dandavisint so i made changes, yes now i don't need to hold button, but speed still jumps up, there are no smoothnes Dec 18 '20 at 19:24

I suggest using a different logic in your program. I would first divide up the different parts of the project:

• A code part, which reads the 3 buttons and sets/increments/decrements a variable based on the button clicks, and
• a code part, which takes a variable as set point and ramps the PWM (analogWrite()) output up or down, until the set point is reached. (So the setpoint is the target speed/PWM value)

This division between "input code" and "output code" is rather common, since it often makes your code better structured and makes it possible to easily add new inputs for one single output.

Let us first look at the second part (ramping the PWM signal up and down). We need a variable, that holds the current PWM value, and one, that holds the current set point (for both we are starting with zero, thus the motor stands still here:

int current_speed = 0;
int speed_setpoint = 0;

We also need a timestamp variable for using millis() to regularly setting the PWM output according to the set point and the corresponding interval (here as a define). Additionally we also define the increment, with which the PWM value should be incremented on each sample:

unsigned long motor_timestamp;
#define MOTOR_SAMPLE_INTERVAL  100
#define MOTOR_SAMPLE_INCREMENT 3

We are doing all this in the global scope. Now in the loop function we first check for millis() and the timestamp (which gives us a regular sample interval for the motor speed) and then check, if the current speed is higher or lower than the setpoint and change the current_speed accordingly. At the end we write the value of current_speed to the output:

if(millis() - motor_timestamp > MOTOR_SAMPLE_INTERVAL){
if(current_speed < speed_setpoint){
current_speed += MOTOR_SAMPLE_INCREMENT;
} else if(current_speed > speed_setpoint){
current_speed -= MOTOR_SAMPLE_INCREMENT;
}
analogWrite(motor, current_speed);
motor_timestamp += MOTOR_SAMPLE_INTERVAL;
}

So now we current_speed of the motor will follow the speed_setpoint, but will only increment or decrement with the speed you set with the MOTOR_SAMPLE_INTERVAL and the MOTOR_SAMPLE_INCREMENT. The new target speed is simply set by giving the variable speed_setpoint a new value. The above code will take care of reaching it.

Now lets look at the first part (reading the buttons for changing a variable). At your current code, you need to hold the button down for the value to change. From your text is guess, that you want a click behavior (1 step in speed for 1 button click, meaning button press and release). For that I suggest using the Bounce2 library, which will handle the buttons - especially the debouncing - for you:

#include <Bounce2.h>
Bounce bounce = Bounce();

void setup(){
bounce.attach( BOUNCE_PIN, INPUT_PULLUP );
// DEBOUNCE INTERVAL IN MILLISECONDS
bounce.interval(5); // interval in ms
}

void loop(){
bounce.update();
if(bounce.fell()){
}
}

Inside the if(bounce.fell()) statement, you would handle the action for the button click, in this case: updating the speed_setpoint variable. Define an increment value for the buttons and do the increment/decrement in the if statements. In global scope:

#define BUTTON_SPEED_INCREMENT 4

In the loop() function:

if(slower_bounce.fell()){
speed_setpoint -= BUTTON_SPEED_INCREMENT;
}
if(faster_bounce.fell()){
speed_setpoint += BUTTON_SPEED_INCREMENT;
}
if(tillend_bounce.fell()){
speed_setpoint = maxspeed;
}

Now you need to put all this together. I will leave that to you. You can then tune the behavior to your liking by changing the defines.

Note the following points:

• The motor will accelerate linear. That should be fitting for you.

• The code will restart the PWM wave at every MOTOR_SAMPLE_INTERVAL. That is ok for a motor when the interval is significantly higher than the PWM interval. When you choose the interval too low (like single digit milliseconds), you need to put the analogWrite() call into the if statements with the increment of speed_setpoint. That will take 1 more statement, but will solve the problem.

• If you choose the MOTOR_SAMPLE_INTERVAL too low, the speed change will get stuttery

If not delay then you'll probably have to use something like an H-Bridge. L298N is a good place to start. You can use PWM to control its speed and its gradual too.