Stepper motor "jitters" when read analog signal from vl6180x TOF sensor

Hi Guys. I am new here and this is my first time to post a topic. :slight_smile:
Thank you for any help.

I am working on do two things together in arduino mega.

  1. Read from two vl6180x sensors through TCA9548A I2C multiplexer.
  2. Control 4 stepper motors with 4 TB6600 sepratly.

They are running good independently, but when combine them in a sketch, a problem happened.

When the arduino read analog signal from the sensor, the stepper will "block" at that moment and then re-rotate. Sensor is read once per second, and the steppers "brake" once per second.
In my opinion, it may because reading from vl6180x take too much time, it brings a "delay" to disturb the running of stepper. But I do not know how to solve it and make the stepper running smoothly. Please give me some help.

#include <Wire.h>
#include "Adafruit_VL6180X.h"
#include <AccelStepper.h>

int speed1 = 1000; 
int speed2 = 1000; 
int speed3 = 1000; 
int speed4 = 1000;

unsigned long currentMillis = 0;
int sensor_Interval = 1000; //SensorDelay
unsigned long pre_distancesensorMillis1 = 500;
unsigned long pre_distancesensorMillis2 = 700;

////----------------------------------------------------------------preset::Distancesensor
#define TCAADDR 0x70

Adafruit_VL6180X sensor = Adafruit_VL6180X();

void tcaselect(uint8_t i) {
  if (i > 7) return; 
  Wire.beginTransmission(TCAADDR);
  Wire.write(1 << i);
  Wire.endTransmission();  
}
  uint8_t range1 = 0; 
  uint8_t range11 = 0; 
  uint8_t range111 = 0; 
  int range1avg = 0;
  uint8_t status1 = 0;
   
  uint8_t range2 = 0;   
  uint8_t range22 = 0; 
  uint8_t range222 = 0;  
  int range2avg = 0;
  uint8_t status2 = 0;

  
//--------------------------------------------------------------------preset::Steppermotor
# define stepper1pul  5
# define stepper1dir  7
# define stepper2pul  51
# define stepper2dir  52
# define stepper3pul  4
# define stepper3dir  6
# define stepper4pul  53
# define stepper4dir  50
AccelStepper stepper1(1, stepper1pul, stepper1dir);
AccelStepper stepper2(1, stepper2pul, stepper2dir);
AccelStepper stepper3(1, stepper3pul, stepper3dir);
AccelStepper stepper4(1, stepper4pul, stepper4dir);

void setup() {
  // put your setup code here, to run once:
Wire.begin();   
Serial.begin(115200);
// ------------------------------------------------------------------  setup::Distancesensor
//    Wire.begin();  
//    Serial.println("\nTCA9548A with two sensor6180X sensors demo.");

    tcaselect(1); // now talking to sensor6180X on 2
    sensor.begin();   // init sensor
    tcaselect(2); // now talking to sensor6180X on 4
    sensor.begin();   // init sensor

// ------------------------------------------------------------------  setup::Steppermotor
pinMode (stepper1pul ,OUTPUT);  
pinMode (stepper1dir ,OUTPUT);  
pinMode (stepper2pul ,OUTPUT);  
pinMode (stepper2dir ,OUTPUT);  

stepper1.setMaxSpeed(2000);
stepper1.setAcceleration(20);
stepper1.setSpeed(speed1);

stepper2.setMaxSpeed(2000);
stepper2.setAcceleration(20);
stepper2.setSpeed(speed2);

stepper3.setMaxSpeed(1250);
stepper3.setAcceleration(20);
stepper3.setSpeed(speed3);

stepper4.setMaxSpeed(1250);
stepper4.setAcceleration(20);
stepper4.setSpeed(speed4);

delay(1000);
}

void loop() {
  
currentMillis = millis();
Steppermotor();
Distancesensor();
}

void Steppermotor(){   

stepper1.setSpeed(speed1);
stepper2.setSpeed(speed2);
stepper3.setSpeed(speed3);
stepper4.setSpeed(speed4);

stepper1.runSpeed();
stepper2.runSpeed();
stepper3.runSpeed();
stepper4.runSpeed();

}

void Distancesensor() {
  
  if (currentMillis - pre_distancesensorMillis1 >= sensor_Interval){
 
  tcaselect(1); 
  range1 = sensor.readRange();  
  range11 = sensor.readRange(); 
  range111 = sensor.readRange(); 
  range1avg = int((range1+range1+range111)/3.0+0.5);
  status1 = sensor.readRangeStatus();
      if (status1 == 0) {      
        Serial.print("sensor6180X on port #1: "); 
        Serial.println(range1avg);
            }
      else{
        Serial.println("System error");
      }
    pre_distancesensorMillis1 = millis();
  }

  if (currentMillis - pre_distancesensorMillis2 >= sensor_Interval){
 
    
  tcaselect(2); 
  range2 = sensor.readRange(); 
  range22 = sensor.readRange(); 
  range222 = sensor.readRange(); 
  range2avg = int((range2+range22+range222)/3.0+0.5);
  status2 = sensor.readRangeStatus();
      if (status2 == 0) {       
        Serial.print("sensor6180X on port #2: "); 
        Serial.println(range2avg);
            }
      else{
        Serial.println("System error");
      }
   pre_distancesensorMillis2 = millis();    
  }
}

What is the highest step frequency that comes out of the stepper output pins?

Or to put it another way, how much time in milliseconds or microseconds can you allocate to the process of taking a sensor measurement such that it doesn't cause a step to be skipped.

For the current speed, each stepper runs 1000 step/second.

For one line “sensor.readRange()” execute, it takes around 12ms... :cry:

Have a look at the vl6180x library to see if there are any non-blocking functions that allow you to initiate a measurement and immediately return. 12 ms later you make a second query asking the sensor to provide the measured value.

If this works then your blocking period will be greatly reduced, however there is still the overhead of I2C transactions and I have no idea if a single I2C transaction can be completed in less than a millisecond.

1000 steps per second gives you just 500 µs assuming equal high and low times.

Or use the timers for the pulse generation instead. Toggle the pin on timer interrupt, count pulses there and then as well (just be aware that you'll be double counting the steps, but that's far more efficient than an if statement and count only on rising). This should not impede on the I2C transmissions as that's done in hardware.

First I tell you I'm not a programmer.

I note:

  1. Looking at your code it seems the "Distancesensor();" function does not modify the any speed.
  2. the code does not read any analog inputs.

My 1st step would be to capture the millis before Distancesensor(); then after Distancesensor(); and see how long it takes. Could print serial out since you don't care about loop time for this test.

Then:
Comment out the Distancesensor(); code and add a delay equal to the Distancesensor(); processing time.

If there is no change then you know it is the delay not the sensor read somehow.

In your tb6600 device will the stepper continue to run once you make the stepperX.runSpeed();? Or does it have to be refreshed at some rate?

Hi mikb55,

Thank you for all the suggestion.I try both libraries from Adafruit and Pololu. But the block still happened. I am not clear about where I need to improve. Below is the code when I use Pololu vl6180x library.

#include <Wire.h>
#include <VL6180X.h>
#include <AccelStepper.h>

int speed1 = 800; 
int speed2 = 800; 
int speed3 = 800; 
int speed4 = 800;

unsigned long currentMillis = 0;
int sensor_Interval = 200; //SensorDelay
unsigned long pre_distancesensorMillis1 = 0;
unsigned long pre_distancesensorMillis2 = 200;

////----------------------------------------------------------------preset::Distancesensor
#define TCAADDR 0x70

VL6180X sensor;

void tcaselect(uint8_t i) {
  if (i > 7) return; 
  Wire.beginTransmission(TCAADDR);
  Wire.write(1 << i);
  Wire.endTransmission();  
}
  uint8_t range1 = 0; 
  uint8_t range11 = 0; 
  uint8_t range111 = 0; 
  int range1avg = 0;
  uint8_t status1 = 0;
   
  uint8_t range2 = 0;   
  uint8_t range22 = 0; 
  uint8_t range222 = 0;  
  int range2avg = 0;
  uint8_t status2 = 0;

  
//--------------------------------------------------------------------preset::Steppermotor
# define stepper1pul  5
# define stepper1dir  7
# define stepper2pul  51
# define stepper2dir  52
# define stepper3pul  4
# define stepper3dir  6
# define stepper4pul  53
# define stepper4dir  50
AccelStepper stepper1(1, stepper1pul, stepper1dir);
AccelStepper stepper2(1, stepper2pul, stepper2dir);
AccelStepper stepper3(1, stepper3pul, stepper3dir);
AccelStepper stepper4(1, stepper4pul, stepper4dir);

void setup() {
  // put your setup code here, to run once:
Wire.begin();   
Serial.begin(115200);
// ------------------------------------------------------------------  setup::Distancesensor
//    Wire.begin();  
//    Serial.println("\nTCA9548A with two sensor6180X sensors demo.");

    tcaselect(1); // now talking to sensor6180X on 2
  sensor.init();
  sensor.configureDefault();
  sensor.writeReg(VL6180X::SYSRANGE__MAX_CONVERGENCE_TIME, 30);
  sensor.writeReg16Bit(VL6180X::READOUT__AVERAGING_SAMPLE_PERIOD, 24);

  sensor.setTimeout(500);
  sensor.stopContinuous();
  delay(300);
  sensor.startRangeContinuous(500);
  
    tcaselect(2); // now talking to sensor6180X on 4
  sensor.init();
  sensor.configureDefault();
  sensor.writeReg(VL6180X::SYSRANGE__MAX_CONVERGENCE_TIME, 30);
  sensor.writeReg16Bit(VL6180X::READOUT__AVERAGING_SAMPLE_PERIOD, 24);

  sensor.setTimeout(500);
  sensor.stopContinuous();
  delay(300);
  sensor.startRangeContinuous(500);

// ------------------------------------------------------------------  setup::Steppermotor
pinMode (stepper1pul ,OUTPUT);  
pinMode (stepper1dir ,OUTPUT);  
pinMode (stepper2pul ,OUTPUT);  
pinMode (stepper2dir ,OUTPUT);  

stepper1.setMaxSpeed(2000);
stepper1.setAcceleration(20);
stepper1.setSpeed(speed1);

stepper2.setMaxSpeed(2000);
stepper2.setAcceleration(20);
stepper2.setSpeed(speed2);

stepper3.setMaxSpeed(1250);
stepper3.setAcceleration(20);
stepper3.setSpeed(speed3);

stepper4.setMaxSpeed(1250);
stepper4.setAcceleration(20);
stepper4.setSpeed(speed4);

delay(1000);
}

void loop() {
  
currentMillis = millis();
Steppermotor();
Distancesensor();
}

void Steppermotor(){   

stepper1.setSpeed(speed1);
stepper2.setSpeed(speed2);
stepper3.setSpeed(speed3);
stepper4.setSpeed(speed4);

stepper1.runSpeed();
stepper2.runSpeed();
stepper3.runSpeed();
stepper4.runSpeed();

}

void Distancesensor() {
  
  if (currentMillis - pre_distancesensorMillis1 >= sensor_Interval){
 
  tcaselect(1); 
 
  Serial.print("sensor6180X on port #1: ");
    Serial.println(sensor.readRangeSingle());
//    Serial.println(sensor.readRangeSingle());
  if (sensor.timeoutOccurred()) { Serial.println(" TIMEOUT"); }

    pre_distancesensorMillis1 = millis();
  }

  if (currentMillis - pre_distancesensorMillis2 >= sensor_Interval){
 
    
  tcaselect(2); 
  Serial.print("sensor6180X on port #2: ");
  Serial.println(sensor.readRangeSingle());
//  Serial.println(sensor.readRangeSingle());
  if (sensor.timeoutOccurred()) { Serial.println(" TIMEOUT"); }
  
   pre_distancesensorMillis2 = millis();    
  }
}

Hi wvmarle,

Thank you for your suggestion. I do not understand how to set the timers instead of the "if". Could you explain more for me? Or do you have example about doing what you talk about? Thank you.

OK, just checked your code a bit better.

You're using AccelStepper, which is limited to about 4,000 steps per second on a 16 MHz Arduino. Four steppers at 1,000 steps per second, that means the stepper library takes up pretty much all the time there is. That's one issue.

Then the reading of the sensor, this is the readRangeContinuious() function of the Polulu library - this one is called from readRangeSingle().

uint8_t VL6180X::readRangeContinuous()
{
  uint16_t millis_start = millis();
  while ((readReg(RESULT__INTERRUPT_STATUS_GPIO) & 0x04) == 0)
  {
    if (io_timeout > 0 && ((uint16_t)millis() - millis_start) > io_timeout)
    {
      did_timeout = true;
      return 255;
    }
  }

  uint8_t range = readReg(RESULT__RANGE_VAL);
  writeReg(SYSTEM__INTERRUPT_CLEAR, 0x01);

  return range;
}

You see that while loop? That's at least a major part of your problem. Solution for that: rewrite the library to not wait, but instead expects to be called frequently.

You should also do some timing tests in your distance sensor code:

void Distancesensor() {
  unsigned long currentMicros = micros();

  if (currentMillis - pre_distancesensorMillis1 >= sensor_Interval){
 
  tcaselect(1);
 
  Serial.print("1: ");
  Serial.println(micros() - currentMicros);
  Serial.print("sensor6180X on port #1: ");
    Serial.println(sensor.readRangeSingle());
//    Serial.println(sensor.readRangeSingle());
  if (sensor.timeoutOccurred()) { Serial.println(" TIMEOUT"); }

  Serial.print("2: ");
  Serial.println(micros() - currentMicros);
    pre_distancesensorMillis1 = millis();
  }

  else if (currentMillis - pre_distancesensorMillis2 >= sensor_Interval){
 
   
  tcaselect(2);
  Serial.print("sensor6180X on port #2: ");
  Serial.println(sensor.readRangeSingle());
//  Serial.println(sensor.readRangeSingle());
  if (sensor.timeoutOccurred()) { Serial.println(" TIMEOUT"); }
 
   pre_distancesensorMillis2 = millis();   
  }
}

This gives the time passed at two points. The first is the time it takes to set your mux. The second is the time it takes for the mux + taking the reading. In microseconds. That should at least give you an idea where all that time is spent. Your next challenge is to bring down the time spent in Distancesensor() to <100µs for all passes, and that's going to be a serious challenge as the I2C reads alone may take more time than that.

Also I made the second if an else if, so you never try to take two readings without giving time to the stepper in between.