How to read temperature values while the stepper motor is moving (non-blocking))

Hi, I am trying to write an Arduino program for a stepper motor and temperature sensors. I am using Arduino MKR1000, two DS18B20 temperature, two photoelectric sensors as switches, and NEMA 17-like stepper motor with an A4988 stepper motor driver.

I want my program to take temperature readings continuously. Also, when a user inputs, let's say 's', I want the stepper motor to go forward until the first switch is activated and then go back until the second switch is activated. Later, it will go further just a little bit until the second switch is deactivated then it will stop. Everything works almost as I want. However, I still have a problem. My problem is while the stepper motor moves I cannot get the temperature readings. I want to get temperature readings while the stepper motor moves. How can I achieve this? I also don't want to use AccelStepper if possible because I think it isn't fully compatible with my stepper motor. Thanks
Here is what I have tried so far:

#include <OneWire.h> // library for DS18B20 temperature sensors
#include <DallasTemperature.h>

// define variables for the temperature sensors, the stepper motor driver, and the photoelectric sensors
#define ONE_WIRE_BUS_1 6
#define ONE_WIRE_BUS_2 7
#define stepPin 8
#define dirPin 9
#define switchLMinus 13
#define switchLPlus 10

// define the speed of the stepper motor (in microseconds per step)
const int stepSpeed = 800;

// Define the state variables
unsigned long lastReading = 0;
const unsigned long interval = 100;

OneWire oneWire1(ONE_WIRE_BUS_1);
OneWire oneWire2(ONE_WIRE_BUS_2);
DallasTemperature sensor1(&oneWire1);
DallasTemperature sensor2(&oneWire2);

void setup() {
  // set the direction and step pin as an output
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);

  // set the limit switch pins as inputs
  pinMode(switchLMinus, INPUT_PULLUP);
  pinMode(switchLPlus, INPUT_PULLUP);

  // setup home position
  // move the stepper motor until limit switch minus is activated
  digitalWrite(dirPin, LOW);
  while (digitalRead(switchLMinus) == LOW) {
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(stepSpeed);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(stepSpeed);
  }

  delay(50);

  // move the stepper motor a little further until limit switch minus is deactivated
  digitalWrite(dirPin, HIGH);
  while (digitalRead(switchLMinus) == LOW) {
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(stepSpeed);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(stepSpeed);
  }

  // start the serial communication
  Serial.begin(115200);

  // start up the temerature library
  sensor1.begin();
  sensor2.begin();
}

void loop() {

  // read temp values
  if (millis() - lastReading >= interval) {
    temperatureReadings();
    lastReading = millis();
  }

  // check if the user has entered any input
  if (Serial.available()) {
    char userInput = Serial.read();
    if (userInput == 's') {
      // Call the flipCheck function
      moveMotor();
    }
  }
}

void moveMotor() {

  // set the direction of the stepper motor to forward
  digitalWrite(dirPin, HIGH);

  // move the stepper motor until limit switch plus is activated
  while (digitalRead(switchLPlus) == LOW) {
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(stepSpeed);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(stepSpeed);
  }

  // reverse the direction of the stepper motor
  digitalWrite(dirPin, LOW);

  // move the stepper motor until limit switch minus is activated
  while (digitalRead(switchLMinus) == LOW) {
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(stepSpeed);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(stepSpeed);
  }

  delay(50);

  // move the stepper motor a little further until limit switch minus is deactivated
  digitalWrite(dirPin, HIGH);
  while (digitalRead(switchLMinus) == HIGH) {
    digitalWrite(stepPin, HIGH);
    delayMicroseconds(stepSpeed);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(stepSpeed);
  }
}

// get temperature values from the sensors
void temperatureReadings() {
  sensor1.requestTemperatures();
  sensor2.requestTemperatures();
  Serial.print(sensor1.getTempCByIndex(0));
  Serial.print(",");
  Serial.println(sensor2.getTempCByIndex(0));
}

The code in movemotor looks compatible with accelstepper

You need to write a non-blocking move motor function that can be called unconditionally every loop (accelstepper can help with that). And you should change your call to move motor to set a global variable for move motor to figure out what it needs to do.

I know I have to do it non-blocking way but I cannot figure it out, the stepper motor blocks the Arduino. Can you help or guide me? I didn't understand your last sentence. Do I have to set the moveMotor() function as a global variable?

I guess he's trying to say, move stepper one small increment each time and after movement read your temps again.

I meant something like:

int moveMotorState = 0; // state variable for what the motor should be doing
...

void loop(){
...
  if (Serial.available()) {
    char userInput = Serial.read();
    if (userInput == 's') {
      // Call the flipCheck function
      moveMotorState = 1; // start moving the motor
      // moveMotor();
    }
  }
  moveMotorNonBlocking();
...
}

and re-write moveMotor() into moveMotorNonBlocking() with a state-machine that moves or not depending on the state variable.

For that, I'd start with AccelStepper's bounce example, adapt it to work with your stepper setup, and then switch directions on limit switches and moveMotorState rather than target, then rename it's loop() into moveMotorNonBlocking() and add a bit of state machine logic to return if moveMotorState == 0, operate like bounce's modified loop().

Can you get this to work with your stepper:

https://www.airspayce.com/mikem/arduino/AccelStepper/Bounce_8pde-example.html

1 Like

Actually, my stepper motor works with the AccelStepper library. However, it doesn't run smoothly. I tired to change the speed and the acceleration speed, it is still slow and not smooth. I will try your suggestion and see if I can make it work.

what about something like

int step;
unsigned long usecLst;

void loop ()
{
    if (step)  {
        unsigned long usec = micros ();
        if (usec - usecLst >= stepSpeed)  {
            usecLst = usec;

            if (LOW == digitalRead (stepPin))
                digitalWrite (stepPin, HIGH);
            else {
                digitalWrite (stepPin, LOW);
                step--;
            }
        }
    }

    // read temp values
    if (millis () - lastReading >= interval) {
        temperatureReadings ();
        lastReading = millis ();
    }

    // check if the user has entered any input
    if (Serial.available ()) {
        char buf [80];
        int n = Serial.readBytesUntil ('\n', buf, sizeof (buf)-1);
        buf [n] = '\0';

        if ('s' == buf [0])  {
            sscanf (buf, "s%d", &step);
            Serial.println (step);
        }
    }
}

Be careful with the temperature sensors, requestTemperatures() is a blocking function that will wait 750mS for the DS18B20 to complete the temperature conversion before returning. The DallasTemperature library has a method for eliminating this delay, which allows you to request the temperature, then later read the temperature from the sensor after allowing time for the sensor to process the request. For a fairly rapid continuous temperature reading, this is generally implemented by requesting the temperature conversion immediately after reading the temperature, and making sure the interval between readings is at least as long as the conversion time of the DS18B20 (varies from 93.75mS for 9-bit resolution to 750mS for 12-bit).

Why do you have the DS18B20 sensors on separate pins? Generally multiple sensors are wired to the same OneWire bus.

2 Likes

Perhaps the best solution is to use an interrupt-driven stepper driver using one of the hardware timers. That way, step pulses are created "in the background" by the hardware. This not only provides much more consistent step pulse timing, but also frees up the foreground code to do many other things "at the same time".

2 Likes

The MobaTools stepper library does non-blocking stepper movement "in the background". The MobaTools stepper library also uses acceleration to achieve higher speeds.

MobaTools library documentation.

2 Likes

With mobatools, try to get this example to work with your setup:

Then adapt it to your limit switches

1 Like

You are totally right. I noticed that when I request temperature values it takes a while to print them on Serial Monitor. Also, if I take temperature values while the stepper motor is running, It substantially slows the Arduino. I understood that I can use sensors.setResolution(9) or similar to lower the precision and wait time at the same moment but won't I lose some precision? Also, I didn't understand what you mean by there is a method for eliminating the delay. Which function does that?

Actually, I am not sure about that is there any disadvantage to using multiple pins for multiple sensors? Thanks.

Do you mean I should use attachInterrupt()?

Using the DallasTemperature library, setWaitForConverstion(false) will disable the delay. When you call requestTemperatures(), it will send the request to the sensor then return immediately. At that point, you save the value of millis(), because it is the responsibility of your code to make sure the required amount of time has passed before calling getTempCByIndex().

If you want to read the temperature every 100mS, that can only be done at 9-bit resolution, which takes 93.75mS. 10-bit resolution needs 187.5mS, 11-bit 375ms, and 12-bit 750mS. At the 100mS interval, you would read the temperature from the previous request, then immediately request a new temperature reading, allowing the sensor to process the request during the 100mS interval.

If you absolutely must have 12-bit resolution at 100mS intervals, you could use eight DS18B20 sensors, staggering the requests by 100mS so that a sensor reading is available every 100mS.

1 Like

I haven't used MobaTools before, but here's a modification of the MobaTools example, modified to simulate a couple limit switches and bounce back and forth:

/*  Example for MobaTools
    Moving a stepper back and forth with limit switches
    Based on https://github.com/MicroBahner/MobaTools/tree/master/examples/_Stepper/back_ForthStepperPause
    Modified to add simulated limit switches, with sim outputs on 10 & 11
    feeding limit switches on 3 & 4 

    Wokwi simulation at https://wokwi.com/projects/362218133861312513
    Wokwi wiring based on https://wokwi.com/projects/327823888123691604 sin
For https://forum.arduino.cc/t/how-to-read-temperature-values-while-the-stepper-motor-is-moving-non-blocking/1115846/11    
*/


#include <MobaTools.h> // https://github.com/MicroBahner/MobaTools

// Adjust pins, steps and time as needed
const byte stepPin = 9;
const byte dirPin  = 8;
const byte lowLimitPin = 3; // low limit switch Normally closed, 
const byte highLimitPin = 4; // low limit switch
const byte LED_LOW = 10; // low limit switch triggered by Sim
const byte LED_HIGH = 11; // high limit switch triggered by Sim

const int stepsPerRev = 200;   // Steps per Revolution ( example with 1/4 microsteps )
long  targetPos = 200;         // stepper moves between 0 and targetpos
long  targetPosLow = 0;         // stepper moves between 0 and targetpos
long nextPos;


MoToStepper myStepper ( stepsPerRev, STEPDIR );
MoToTimer pause;                    // Pause between stepper moves
bool stepperRunning;

int limitHigh = HIGH ;  // Normally closed, pullup
int limitLow = HIGH ;  // Normally closed, pullup



void setup() {
  myStepper.attach( stepPin, dirPin );
  myStepper.setSpeed( 60 );  // 60 Rev/Min
  myStepper.setRampLen( 5 );
  stepperRunning = true;
  pinMode(LED_LOW,OUTPUT);
  pinMode(LED_HIGH,OUTPUT);
  pinMode(lowLimitPin,INPUT);
  pinMode(highLimitPin,INPUT);
  Serial.begin(115200);
}

void loop() {
  if ( stepperRunning ) {
    // Wait till stepper has reached target, then set pause time
    if ( !myStepper.moving() ) {
      // stepper has reached target, start pause
      pause.setTime( 1 );
      stepperRunning = false;
    } else { // stepper moving
       if (digitalRead(highLimitPin) == HIGH && digitalRead(dirPin)==HIGH ){
          targetPos = min(targetPos,myStepper.currentPosition()); // update high target
          myStepper.writeSteps(targetPosLow);
       } else if (digitalRead(lowLimitPin) == HIGH && digitalRead(dirPin)==LOW ) {
         targetPosLow = max(targetPosLow,myStepper.currentPosition()); // update high target
         myStepper.writeSteps(targetPos);
       }
    }
  } else {
    // stepper doesn't move, wait till pause time expires
    if ( pause.expired() ) {
      // pause time expired. Start stepper in opposite direction
      if ( nextPos == 0 ) {
        nextPos = targetPos;
      } else {
        nextPos = 0;
      }
      myStepper.moveTo( nextPos );
      stepperRunning = true;
    }
  }

  simLimitSwitches();
  report();
  // The sketch is not blocked while the stepper is moving nor while it is stopped.
  // Other nonblocking  tasks can be added here
}

void simLimitSwitches(void){
  limitHigh = myStepper.currentPosition() > 150 ? LOW : HIGH ;
  limitLow = myStepper.currentPosition() < 50 ? LOW : HIGH ;
  digitalWrite(LED_LOW,limitLow == LOW);
  digitalWrite(LED_HIGH,limitHigh == LOW);
}

void report(void){
  uint32_t interval = 250;
  static uint32_t last = - interval;
  if(millis() - last < interval) return;
  last += interval;
  Serial.print("Speed:");
  Serial.print(myStepper.getSpeedSteps());
  Serial.print(" Pos:");
  Serial.print(myStepper.currentPosition());
  Serial.print(" stepsToGo:");
  Serial.print(myStepper.stepsToDo());
  Serial.print(" dirPin:");
  Serial.print(digitalRead(dirPin));
  Serial.print(" Speed:");
  Serial.print(((long)myStepper.getSpeedSteps())*(digitalRead(dirPin)?1:-1));
  Serial.println();
   
}

One thing I found difficult with MobaTools stepper was determining the current direction of motion, which I ended up reading directly from the stepper driver direction input pin.

In any case, here's a sloppy/quick example of non-blocking, interrupt based stepper motion control with MobaTools, using (simulated) limit switches to switch direction. There are a couple routines making use of the non-blocked-ness of the loop(): report() to periodically print out the current state of the system, and simLimitSwitches() to simulate limit switches based on the stepper position.

If this was the path forward, the next steps I would take would be to move this example's stepper stuff out of loop() and into a moveMotorNonBlocking(){...}; function, and add a doTemperatures() function encapsulating a periodic check of the temperatures (along the lines of report()).

Are the temperatures and the stepper oscillation supposed to interact somehow?

1 Like

Sorry for the late reply. I finally found that the issue was with my stepper motor. So I ordered a new one and it took a while to get the stepper motor. Now, as you mentioned before I used the AccelStepper library, and now, everything works perfectly fine. Thanks for the help.

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