Controller for Milling Machine with Stepper Motor and Display

Dear All,

I put together a controller for a stepper motor with Arduino Due. I have two buttons to move the table back and forward at a speed set with a pot. All works fine.
Now, connecting an I2C display, it's tempting to display the position of the table, that can be easily calculated. All good.
Now the troubles: if I try to have a live update of the data on the display the stream of pulses that drives the motor is interrupted and the movement is very slow and not fluid. Basically useless.
I can see this in practice and also looking at the pulses with a scope.
The question is: is there a smarter way to code the thing so that the pulse are continuous while the display is updated ?

The attached code has provision for three axes, but I am presently working only on X. I tried both to update the display with a function or with a direct command in the loop, the results are the same.

Here is the code:

/*
Stepper Motor Control
20180508
First version:
Display
Acceleration
Speed Selection
Only one axe

*/

#include <Stepper.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 20, 4);

#define RIGHT 51
#define LEFT 50

#define X_PULSE 22
#define X_DIR 23
#define X_ENA 24

#define Y_PULSE 26
#define Y_DIR 27
#define Y_ENA 28

#define Z_PULSE 30
#define Z_DIR 31
#define Z_ENA 32

#define AXE_SELECT 48
#define ZERO_PIN 49

#define START_SPEED 100 //50 mm/min
#define MAX_SPEED 2750 // 1400 mm/min
#define SPEED_INCREMENT 5
#define STEPS_PER_REVOLUTION 400
#define POSITION_INCREMENT 0.00125
#define MIN_STEPS 1
#define SET_SPEED_TOLERANCE 5

double Xpos = 0.0 ;
double Ypos = 0.0 ;
double Zpos = 0.0 ;
int axe = 0 ;
bool Xchange = true ;
bool Ychange = true ;
bool Zchange = true ;
bool Axechange = true ;
bool Speedchange = true ;

int SetSpeed = START_SPEED;
int tempSpeed = START_SPEED ;

Stepper myStepper(STEPS_PER_REVOLUTION, X_PULSE,X_DIR);

void read_Speed()
{
int temp = map(analogRead(A0), 0, 1023, START_SPEED, MAX_SPEED);
if (abs(temp-SetSpeed) > SET_SPEED_TOLERANCE )
{
SetSpeed = temp;
update_display();
Speedchange = true ;
}

}

void selectAxe()
{
if (digitalRead(AXE_SELECT)==false) axe++;
if (axe > 1) axe = 0 ;
}

void print_label()
{
lcd.clear();
lcd.setCursor(0,0);
lcd.print("X:");
lcd.setCursor(0,1);
lcd.print("Y:");
lcd.setCursor(0,2);
lcd.print("Speed:");
/*
lcd.setCursor(0,3);
lcd.print("Moving Speed:");
*/
}

void update_display()
{
if (Xchange)
{
lcd.setCursor(2,0);
if (Xpos>0) lcd.print("+");
else lcd.print("-");
if (abs(Xpos) < 100 ) lcd.print(" ");
if (abs(Xpos) < 10 ) lcd.print(" ");
lcd.print(abs(Xpos),3);
lcd.print(" ");
bool Xchange = false ;
}

if (Ychange)
{
lcd.setCursor(2,1);
if (Ypos>0) lcd.print("+");
else lcd.print("-");
if (abs(Ypos) < 100 ) lcd.print(" ");
if (abs(Ypos) < 10 ) lcd.print(" ");
lcd.print(abs(Ypos),3);
lcd.print(" ");
bool Ychange = false ;
}

if (Axechange)
{
lcd.setCursor(19,0);
switch (axe) {
case 0:
lcd.print("X");
break;
case 1:

lcd.print("Y");
break;
case 2:
lcd.print("Z");
break;
}
bool Axechange = false ;
}

if (Speedchange)
{
lcd.setCursor(6,2);
//lcd.rightToLeft();
if (SetSpeed < 1000 ) lcd.print(" ") ;
if (SetSpeed < 100 ) lcd.print(" ") ;
lcd.print(SetSpeed / 2 );
lcd.print("mm/min ");
bool Speedchange = false ;
}/*
lcd.setCursor(13,3);
if (tempSpeed < 1000 ) lcd.print(" ") ;
if (tempSpeed < 100 ) lcd.print(" ") ;
lcd.print(tempSpeed);
*/
}

void setup() {

myStepper.setSpeed(START_SPEED);

pinMode(RIGHT,INPUT_PULLUP);
pinMode(LEFT,INPUT_PULLUP);
pinMode(X_ENA,OUTPUT);
pinMode(Y_ENA,OUTPUT);
pinMode(AXE_SELECT,INPUT_PULLUP);

digitalWrite(X_ENA,HIGH); //HIGH is lose
digitalWrite(Y_ENA,HIGH); //HIGH is lose

// initialize the LCD
lcd.begin();
lcd.clear();
// Turn on the blacklight and print a message.
//lcd.backlight();
lcd.setCursor(3,0);
lcd.print("CNC Controller");
lcd.setCursor(6,2);
lcd.print("20180524");

delay(2000);
print_label();
read_Speed();
update_display();

// initialize the serial port:
//Serial.begin(9600);
}//end setup

void loop() {

// step one revolution in one direction:
while (digitalRead(RIGHT)==true && digitalRead(LEFT)==false) //clockwise
{

digitalWrite(X_ENA,LOW);
myStepper.step(MIN_STEPS);
Xpos += POSITION_INCREMENT;
Xchange = true ;
if (tempSpeed < SetSpeed)
{
tempSpeed +=SPEED_INCREMENT;

myStepper.setSpeed(tempSpeed);

}

//update_display();
}

while (digitalRead(LEFT)==true && digitalRead(RIGHT)==false)//counter-clockwise
{
digitalWrite(X_ENA,LOW);
myStepper.step(-MIN_STEPS);

Xpos -= POSITION_INCREMENT;
Xchange = true ;
if (tempSpeed < SetSpeed)
{
tempSpeed +=SPEED_INCREMENT;

myStepper.setSpeed(tempSpeed);
}
// update_display();

}

digitalWrite(X_ENA,HIGH);
digitalWrite(Y_ENA,HIGH);
selectAxe();
tempSpeed = START_SPEED ;
myStepper.setSpeed(tempSpeed);
read_Speed();
update_display();

} //end loop

Thanks,

Davide

Maybe I'm being dense but I don't see where you actually take a step.

Your code looks reasonable - you only update the part of the display which changed. That should be fast. I would expect that this is possible to do in between the steps of the stepper motor. However it seems that it is slower than one step. That means the usual trick of updating only once every 10 or 100 steps won't work as that one step will be slowed by the screen-update function.

Can you write individual characters to the display without using the print() function? Maybe you can calculate the digits to display and then write only one digit between steps?

If it's really a problem updating the display between steps then you will have to offload one of those functions. The more difficult (but more correct) one is to use the hardware timers on your Arduino to send the steps to the motors. Then you just have to set the speed and the actual steps will occur without bothering your main program at all. I have code to do this on a Teensy but not on other Arduinos. The easier one is to use a second Arduino for the display but that's introducing unnecessary complexity for a really simple LCD task.

Next time use code tags. Read the how-to-use-this-forum.

Thanks for the answer.

I was thinking about the two arduino setting, but I have the feeling it will not make a difference, as also if I try to output the position to the serial output, I get the same problem.

I'll look into hardware timers. One solution that your answer suggested me is to update the display at a frequency proportional to the speed, so that let's say I have one update every cm when it goes fast and one update every mm when it is slow.

Will try this.

Thanks,

D.

Nikon1975:
Thanks for the answer.

I was thinking about the two arduino setting, but I have the feeling it will not make a difference, as also if I try to output the position to the serial output, I get the same problem.

Your feeling is wrong.

You could change the "while" loops to "if" and let loop() take care of the iterations.

Paul

To make it easy for people to help you please modify your post and use the code button </> so your code looks like this and is easy to copy to a text editor. See How to use the Forum

Your code is too long for me to study quickly without copying to a text editor.

Also please use the AutoFormat tool to indent your code for easier reading.

...R

I can see something weird in your code: Firstly, you declare Xchange, Ychange, ..., Speedchange as global boolean variables, then in update_display() you declare local variables with the same names that you update as false....

See lines 111, 123, 153.