Stepper Motor Pulses & 16x2 LCD Screen Challenges

Hi All,
I have a MEGA connected to a TB6600 stepper motor driver. I send pulses to the driver every 33 microseconds (using 1/16 step driver mode). This motor drives a linear actuator that moves outward 87mm and back 87mm at a speed of about 40mm/sec. I've been asked to provide a LCD screen showing the current position of the actuator slider. And, YES, I know that it's impossible to see the LCD screen updates from 00 to 40 in one second and have explained such (and even demoed it), but never the less, it has been deemed required for this project.

It seems that the LCD screen (16x2 LCD Screen) updates are way too slow; ranging from around 2000 microseconds to 25000 microseconds from the research I've done on this forum and others. Every 640 pulses of the stepper motor equates to 1mm of linear slider travel. So, I only need to update the 2 LCD characters (from 00 to 87) every 640 pulses, which is 21,120 microseconds.

Here is where my issue develops: I can't do two things at once. I have to keep the motor running and update the LCD screen, which the Mega can't do (at least I don't know how it could do this as a single processor board). So, to get around this, I simply output another pulse signal (different pin) when I send the motor pulse signal. Then, with a 2nd board (UNO instead of Mega), I capture through an interrupt the pulse and count to 640. I then update the LCD screen. While this allows the motor to keep running on the Mega and the LCD screen to be updated at the same time, the motor pulses from the Mega get ignored by the UNO during the LCD screen update process. So, I end up missing about 30+ pulses for every LCD screen update.

I decided to create an off-line demo using two UNOs so I could experiment more.

Below is the code for the "Master" stepper motor driver UNO.

byte directionPin = 4;
byte stepPin = 3;
byte mega_uno_send_pin = 7;
byte startbuttonPin = 9;
byte bluebuttonPin = 8;
byte stopbuttonPin = 2;
int microsbetweenSteps = 65;
unsigned long Motor_Pulse_Counter = 0;
unsigned long curMicros;
unsigned long prevStepMicros = 0;
int Motor_Rotation_Direction = 0;
int emergencyStopFlag = 0;

void setup() {

  Serial.begin(9600);

  pinMode(directionPin, OUTPUT);
  pinMode(stepPin, OUTPUT);
  digitalWrite(directionPin, HIGH);

// From Mega to Uno motor pulse pin
  pinMode(mega_uno_send_pin, OUTPUT);
  digitalWrite(mega_uno_send_pin, HIGH);

// Stop button interrupt setup
  pinMode(stopbuttonPin, INPUT_PULLUP);

//  Start button pin
  pinMode(startbuttonPin, INPUT_PULLUP);
  pinMode(bluebuttonPin, INPUT_PULLUP);
  delay(300);

// Wait for start button to be pressed
  while (digitalRead(stopbuttonPin) == HIGH && digitalRead(startbuttonPin) == HIGH)
  {
    if (digitalRead(startbuttonPin) == LOW)
    {
     microsbetweenSteps = 65;
    }
      if (digitalRead(stopbuttonPin) == LOW)
    {
      microsbetweenSteps = 300;
    }
  }
// Stop button interrupt setup
  attachInterrupt(digitalPinToInterrupt(stopbuttonPin), emergency_stop_flag, RISING);
}

void loop() {
  // Check if emergency stop button has been pressed.
  if (emergencyStopFlag == 1)
  {
    while (digitalRead(stopbuttonPin) == LOW)
    {
    }
    emergencyStopFlag = 0;
  }
// Nema 17 stepper motor control in microseconds (this program only works with 1/16 microstepping).
  curMicros = micros();
  singleStep();
}

// <<<<<<<<<<<<<<<<<<<< Pulse motor 1 time (1/16 step) >>>>>>>>>>>>>>>>>>>>
void singleStep() {
// Provide 1 pulse to the motor after microsBetweenSteps microseconds have elapsed.  
    if (curMicros - prevStepMicros >= microsbetweenSteps)
    {
// Designed lead screw travel = 83mm from HOME offset position (4mm from HOME position)
// For 1/16 microsteps, and at 1.8Β° per step motor, there are 3200 pulses per shaft revolution.
// For a lead screw pitch = 5mm, this gives 16.6 shaft rotations for 83mm travel. This is 53120 motor pulses for the 83mm travel.      
        Motor_Pulse_Counter = Motor_Pulse_Counter + 1;
        prevStepMicros = curMicros;
        digitalWrite(stepPin, HIGH);
        digitalWrite(stepPin, LOW);
        digitalWrite(mega_uno_send_pin, HIGH);
        digitalWrite(mega_uno_send_pin, LOW);
// Change motor direction at end of travel.        
        if (Motor_Pulse_Counter == 55680)
        {
          Motor_Pulse_Counter = 0;
          if (Motor_Rotation_Direction == 0)
          {
            Motor_Rotation_Direction = 1;
            digitalWrite(directionPin, LOW);
          }
          else
          {
            Motor_Rotation_Direction = 0;
            digitalWrite(directionPin, HIGH);
          }
        }
    }
}
// *******************************************************************************************

// <<<<<<<<<<<<<<<<<<<< Emergency stop button pressed (interrupt routine) >>>>>>>>>>>>>>>>>>>>
void emergency_stop_flag() {
  emergencyStopFlag = 1;
}
// *******************************************************************************************

Below is the code for the "Slave" LCD Screen Update UNO.

/*
  LiquidCrystal Library

 Demonstrates the use a 16x2 LCD display.  The LiquidCrystal
 library works with all LCD displays that are compatible with the
 Hitachi HD44780 driver. There are many of them out there, and you
 can usually tell them by the 16-pin interface.

  The circuit:
 * LCD RS pin to digital pin 7
 * LCD Enable pin to digital pin 8
 * LCD D4 pin to digital pin 9
 * LCD D5 pin to digital pin 10
 * LCD D6 pin to digital pin 11
 * LCD D7 pin to digital pin 12
 * LCD R/W pin to ground
 * LCD VSS pin to ground
 * LCD VCC pin to 5V
 */

// include the library code:
#include <LiquidCrystal.h>

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

 byte mega_receive_pin = 3;
// For 1/16 step and lead screw = 5mm: slider position 1mm movement for every 640 pulses.
unsigned long Motor_Pulse_Counter = 0;
int slider_position = 0;
int slider_position_previous = 0;
int direction_switch = 0;
// Slider direction: 0 = right, 1 = left.
int slider_direction = 0;
int arrow_location = 1;
int arrow_counter = 0;

void setup() {

  pinMode(mega_receive_pin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(mega_receive_pin), mega_pulse_counter, LOW);

  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Absolute Encoder");
  lcd.setCursor(0, 1);
  lcd.print(slider_position);
  lcd.print(" ");
  lcd.setCursor(10,1);
  lcd.print("--->");
}

void loop() {

  if (slider_position != slider_position_previous)
  {
    if (slider_position < 10)
    {
      lcd.setCursor(0, 1);
      lcd.print(slider_position);
      lcd.print(" ");
    }
    else
    {
      lcd.setCursor(0, 1);
      lcd.print(slider_position);
    }
  }
  if (direction_switch == 1)
  {
    if (slider_direction == 0)
    {
      lcd.setCursor(10,1);
      lcd.print("--->");
    }
    else
    {
      lcd.setCursor(10,1);
      lcd.print("<---");
    }
    direction_switch = 0;
  }
}

void pulse_counter()
{
    if (slider_direction == 0)
    {
      Motor_Pulse_Counter = Motor_Pulse_Counter + 1;
      slider_position = Motor_Pulse_Counter / 640;
    }
    else
    {
      Motor_Pulse_Counter = Motor_Pulse_Counter + 1;
      slider_position = 87 - (Motor_Pulse_Counter / 640);
    }

//   Serial.println((String)"Motor Pulse Counter = " + Motor_Pulse_Counter);

    if (Motor_Pulse_Counter == 55680)
    {
      Motor_Pulse_Counter = 0;
      direction_switch = 1;
      if (slider_direction == 0)
      {
        slider_direction = 1;
      }
      else
      {
        slider_direction = 0;
      }
    }
}

void mega_pulse_counter()
{
  slider_position_previous = slider_position;
  pulse_counter();
}

I have been researching dual core processors to see if this could solve my challenges. I looked at the Giga and thought might work until I read that the transfer of data between the two cores is by a serial port. This will not work for me as the speed of the pulses is 33 microseconds - way to fast for any serial port. So, I'm still hunting for an Arduino solution as I really don't want to have to go to another platform and completely redo all my Mega code that is over 500 lines.

I would be grateful for the communities thoughts.
Thank you.
Robert

Maybe I am missing something, but stepper motors don't need you to 'be there', just tell it to run for x steps, or even distance while you update the screen. Maybe a timer based motor and timer ISR in synch to force you away from the LCD in order to give the motor another command. Also an end micro switch or mag switch, or hall sensor or light based connected to another ISR in order to halt the motor.
I am a lightweight in this topic, but maybe a heavyweight will add more to this or tell you I am full of bull pucky.

1 Like

YOu appear to never set slider_position_previous to anything, so always zero.

image

Your slider_position_previous must declared with volatile keyword if it supposed to change inside an interrupt

Ok, but I don't think you want to be doing such things in an interrupt function. Do it in the main code.

1 Like

Yea. You're right. I should move the slider_position_previous outside of the interrupt. Good point. Thanks.
The LCD update timeframe kind of stopped me in my tracks to refine the code as I've been searching and searching for a solution.

From the research I've done, stepper motor driver libraries, like AccelStepper (AccelStepper Library) have essentially two command options:

  1. runToPosition() - this moves the stepper motor XX steps and then stops. However, this is a blocking command - meaning nothing else, like LCD updates, can happen while this is running.
  2. run() - this moves the stepper motor 1 step at the set speed, etc. This is a non-blocking command, but is essentially what I have implemented in my code. Instead of having the AccelStepper library overhead, I create my own timer counter (micros) and issue step pulse commands at an interval associated with the speed / acceleration that I want.

While the "run()" or my own code built in stepper pulse counter, there is no way to get an LCD update when the pulse to the motor is 33 to 65 microseconds. A typical LCD display update theoretically can take as little as 1400 microseconds, but in reality takes closer to 25000 microseconds with the standard LCD library. So, my original posting situation still is unsolved:

"How to continually command a stepper motor at a speed of about 570 RPM at 1/16 steps - meaning a step pulse must be sent every 33 microseconds. Then, after 640 pulses, I need to update an LCD screen with the linear actuator's positional information without stopping the motor pulses.

Much appreciate any ideas.

Don't 3D printers do that and more?

Timers.

https://www.pjrc.com/teensy/td_libs_TimerOne.html

#include <TimerOne.h>

// This example uses the timer interrupt to blink an LED
// and also demonstrates how to share a variable between
// the interrupt and the main program.

const int led = LED_BUILTIN;  // the pin with a LED
const int stepPin = 9; //

void setup(void)
{
  pinMode(led, OUTPUT);
  pinMode(stepPin,OUTPUT);
  Timer1.initialize(33);
  Timer1.pwm(stepPin, 512);
  Serial.begin(9600);
}

// The interrupt will blink the LED, and keep
// track of how many times it has blinked.
int ledState = LOW;

void loop(void)
{
  digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) == HIGH ? LOW : HIGH);
  delay(100);
}

(code dropped into UnoScope - Wokwi ESP32, STM32, Arduino Simulator and move the wire from pin 13 to pin 9.)

So, the updates occur approximately 47.35 times per second. that translates to about 2,841 updates per minute or 568 words per minute. Here’s a breakdown of the average reading speeds by age groups:

  • 6th-8th Grade (Spring):
    Ages: 11, 12, 13, 14 years old
    Average Reading Speed: 150 – 204 words per minute (wpm)
  • High School:
    Ages: 14, 15, 16, 17, 18 years old
    Average Reading Speed: 200 – 300 words per minute (wpm)
  • College:
    Ages: 18-23 years old
    Average Reading Speed: 300 – 350 words per minute (wpm)
  • Adults:
    Ages: 18 years and older
    Average Reading Speed: 220 – 350 words per minute (wpm)

These ranges give an overview of typical reading speeds across different educational levels and age groups.

Knowing this who is going to be able to read at that rate. Note this does not factor in the actual time the display is updating.

Reference: Average Reading Speed (WPM) by Age and Grade Level

1 Like

As mentioned in my original post, I realize it's impossible for anyone to actually see 40 number updates in one second. I even demoed it to numerous people but since this is for a trade show demo, it was deemed necessary to have an LCD screen for a marketing reason (I disagree, but them's the breaks. LOL).

I think you need to use Arduino's pwm function to generate pulses for the stepper.
Arduino has tone(); noTone(); functions.
Using these functions you can control the stepper asynchronously.
I can give you a complete answer to your problem.

Interesting idea. If you have some snippets of code, I'd appreciate as I've never used that function to create a pulse signal to the stepper motor driver.
Thanks.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.