Using Wire.h to trigger a stepper motor routine on slave

I'm struggling to get a stepper motor function to run after sending a command via the Wire.h library. I'm controlling the motors with AccelStepper, and driving this between two Arduino Mega boards.

I'm using Wire.onReceive() function to try and initiate the motor stepping function. After sending the trigger from the Master device, the driver board LEDs light up, and I can hear the motor buzzing, but the motor does not step.

When not driving this through I2C, but calling the function directly in the loop() of the Slave, the motor steps as expected.

My best guess is that using the Wire.h function is impacting the timing for the AccelStepper step execution. But I've not been able to find any clues searching the forum. Appreciate the advice!

Master Code:

#include<Wire.h>

char user_input;

void setup() {

  Serial.println();
  Serial.begin(9600); 
  Wire.begin();     
}

void loop() {

  while (Serial.available()) {
    user_input = Serial.read(); 
    if (user_input == '1'){
    Wire.beginTransmission(1);
    Wire.write(int(0));
    Wire.endTransmission();
    }

    else {
      Serial.println("Invalid option entered.");
    }
  }
}

Slave Code:
(I have an array of 12 motors defined below, but am only trying to step motor-0 for this demo)

#include <AccelStepper.h>
#include<Wire.h>
#define HALFSTEP 8

uint8_t MotorPins[][12] = {
  {2, 6, 10, 14, 47, 22, 23, 30, 31, 38, 39, 48},
  {3, 7, 11, 15, 49, 24, 25, 32, 33, 40, 41, 46},
  {4, 8, 12, 16, 51, 26, 27, 34, 35, 42, 43, 52},
  {5, 9, 13, 17, 53, 28, 29, 36, 37, 44, 45, 50}};

const uint8_t NumberOfSteppers = (sizeof(MotorPins[0]) / sizeof(MotorPins[0][0])); // returns #cols from MotorPins array
AccelStepper *stepperPtrArray[NumberOfSteppers];

float MaxSpeed = 1200;
float Accel = 1000;
float Speed = 1400;
long RunOut = -2048 * 1.85;

uint8_t x;
uint8_t StepperNumber = 0; // Selecting the first motor in the above array for this demo

void setup() {
  for (x = 0; x < NumberOfSteppers; x++){
    stepperPtrArray[x] = new AccelStepper(HALFSTEP, MotorPins[0][x], MotorPins[2][x], MotorPins[1][x], MotorPins[3][x]);// NOTE: The sequence 1-3-2-4 is required for proper sequencing of 28BYJ48
    stepperPtrArray[x]->setMaxSpeed(MaxSpeed);
    stepperPtrArray[x]->setAcceleration(Accel);
    stepperPtrArray[x]->setSpeed(Speed);
  }

 Serial.begin(9600); 
 Wire.begin(1);    
    
 Wire.onReceive(receiveEvent); // I've also tried having this call the StepMotorForward() directly, but results are the same

}

void loop() {
//receiveEvent();  // This is here to verify the step function works correctly outside of Wire.onReceive();
delay(1000);
}

void receiveEvent(){
  while(Wire.available())
  {
    StepperNumber = Wire.read();
  }
  StepMotorForward();

}


void StepMotorForward() {
  
  stepperPtrArray[StepperNumber]->setCurrentPosition(0);
  stepperPtrArray[StepperNumber]->moveTo(-RunOut);
  stepperPtrArray[StepperNumber]->setSpeed(Speed);
  
  while (abs(stepperPtrArray[StepperNumber]->distanceToGo()) > 0) {
          stepperPtrArray[StepperNumber]->runSpeedToPosition();
      } 
      
  stepperPtrArray[StepperNumber]->disableOutputs();
}

That's a bizarre way of formatting your motorPins array. Why 4 rows and 12 columns? Makes more sense to have 4 columns and 12 rows. That way you can add / remove a stepper by adding / removing a row.

True.

gfvalvo:
That's a bizarre way of formatting your motorPins array. Why 4 rows and 12 columns? Makes more sense to have 4 columns and 12 rows. That way you can add / remove a stepper by adding / removing a row.

Fair point.

Any inputs on the question at hand?

Your receiveEvent() function is actually an ISR. I wouldn't be trying to do stepper stuff from interrupt context. Instead set a (volatile) flag to be picked up in your loop() code. Also have a (volatile) variable telling you what stepper to move. Once you see the flag in your main loop(), start the appropriate stepper moving and clear the flag. I'd also print the value received form I2C to confirm you're getting what you think you are.

gfvalvo:
Your receiveEvent() function is actually an ISR. I wouldn't be trying to do stepper stuff from interrupt context. Instead set a (volatile) flag to be picked up in your loop() code. Also have a (volatile) variable telling you what stepper to move. Once you see the flag in your main loop(), start the appropriate stepper moving and clear the flag. I'd also print the value received form I2C to confirm you're getting what you think you are.

Ah, I see... It is making sense now (after some reading on ISR), and works great with your suggested method.

Many thanks!

Hi,
I'm new and don't understand solution of problem.
down is my code.
Master sends movement (float) to slave arduino over I2C every few seconds (3 or more).
Data on master LCD and slave LCD is correct.
When I uncomment "myStepper.step(800);" whole I2C bus stops.

I want to run stepper on Slave arduino for requested rotation and then to be ready for new data from master and stepper rotation.
Arduino Uno board.

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

LiquidCrystal lcd(8, 9, 10, 11, 12, 13);

const int SteppsForCircle = 200;
float steps;
String inData;
float quonity;
Stepper myStepper(SteppsForCircle, 1, 2, 3, 4);// 4, 5, 6, 7);

void setup(){
Serial.begin(9600);
Wire.begin(1);
Wire.onReceive(receiveMovement);
lcd.begin(16,2);
}

void receiveMovement(){
inData = "";
while (Wire.available()) {
char c = Wire.read();
inData = inData + c;
}
Wire.end();
Serial.print(" quonity "); Serial.println(inData);
calculate();
}

void calculate (){
quonity = inData.toFloat();
lcd.clear();
lcd.setCursor(1,0);
lcd.print (quonity, 3);
steps = 40000 * quonity;
myStepper.setSpeed(160);

//
myStepper.step(steps);
//

}

void loop(){}