(Solved) Step Motor Jitter caused by slow loop execution?

I'm posting this same question here with a link to the post in my step motors post because I believe it to be related to the way I coded it.

Please read this post.
http://forum.arduino.cc/index.php?topic=214360.msg1569400#msg1569400

Stepper Motor Jittter is usually caused by an ISR taking too much time. The stepper pulses are interrupt driven so a long ISR (such as SoftwareSerial send or receive) will cause delays in the pulse generation. Since the pulses are measures in microseconds it doesn't take much to cause a long or short pulse.

Finally someone speaking to this issue from a direction that appears they get whats actually going on with it.

But the question is this... What programming technique do I need to use to minimize this, while simultaneously being able to maintain active input checking.

I can't imagine I'm the first person to want to make a step motor move smoothly and precisely with active user input monitoring.

Any suggestions?

Without reading or rewriting your code, generally you set a flag and maybe record some critical info in the ISR and have a routine in loop() act on the flag (which you clear right away).
Note that this requires that none of your loop() code blocks execution while waiting for anything or runs a long sequence. In either of those cases the code should be broken down to smaller parts that run one after the other or set a time or condition (the thing waited for) when the next part picks up and runs.
If you have non-critical times like when the motor isn't going to run, do serial prints then rather than just any time.

If you have not, learn BlinkWithoutDelay and how to use Finite State Machine style code.

So I made this slight change to my code to remove the Presumed serial read, that nunchuck.update is using. The motor moves just as smooth as if it had no user input function at all.. In short.. Jitter gone. So Yes John you were correct on where the slowdown is coming from.

But clearly I would still like to check my controllers state often, preferably without introducing artificially state checking code, like a counter/timer and not check as often... Potentially missing user input changes, creating control lag...

Step in the right direction, thanks again John.... but solution is???? hmmm

void loop()
{
  //nunchuk.update();
  //int analogYraw = nunchuk.analogY;
  int analogYraw = 225;
  //Serial.print(nunchuk.analogY, DEC);Serial.print(' ');

GoForSmoke:
Without reading or rewriting your code, generally you set a flag and maybe record some critical info in the ISR and have a routine in loop() act on the flag (which you clear right away).
Note that this requires that none of your loop() code blocks execution while waiting for anything or runs a long sequence. In either of those cases the code should be broken down to smaller parts that run one after the other or set a time or condition (the thing waited for) when the next part picks up and runs.
If you have non-critical times like when the motor isn't going to run, do serial prints then rather than just any time.

If you have not, learn BlinkWithoutDelay and how to use Finite State Machine style code.

Thanks... I'm confident this is the answer I'm looking for.... Makes what I'm doing more complicated... but makes sense that its needed.... I'm open to other suggestions as well.. I'm going to start looking into what you sad here. Thanks GoForSmoke

Looked into all my included libraries, and looked at how the motor stepping is handled... All of it is handled with Timers to not keep the processor tied up waiting. I learned alot looking into that concept all together. Thanks again. So I ask.

All I'm really doing is a nunchuck.update call, then I run an appropriately setup Stepper.setSpeed followed by a Stepper.step. Both Stepper functions setup with millisecond timers in those libraries. Again the delay comes from nunchuck.update. Below is the code for what its doing:

void ArduinoNunchuk::update()
{
  int count = 0;
  int values[8];

  Wire.requestFrom(ADDRESS,8);

  while(Wire.available())
  {
    values[count] = Wire.read();
    count++;
  }

 ArduinoNunchuk::analogX = values[0];
 ArduinoNunchuk::analogY = values[1];
 ArduinoNunchuk::accelX = values[2];
 ArduinoNunchuk::accelY = values[3];
 ArduinoNunchuk::accelZ = values[4];
 ArduinoNunchuk::zButton = values[5];
    
  ArduinoNunchuk::accelX = (values[2] << 2) | ((values[5] >> 2) & 3);
  ArduinoNunchuk::accelY = (values[3] << 2) | ((values[5] >> 4) & 3);
  ArduinoNunchuk::accelZ = (values[4] << 2) | ((values[5] >> 6) & 3);
  ArduinoNunchuk::zButton = !((values[5] >> 0) & 1);
  ArduinoNunchuk::cButton = !((values[5] >> 1) & 1);

  ArduinoNunchuk::_sendByte(0x00, 0x00);
}

My assumption is, that my delay is coming from Wire.read() (looking into Wire.read now) The question is this... if my IC2 wire.read() command takes longer then a single step.... Wont that mean with the stepper stepping, there Never be a chance to check my input with out causing a hiccup in the steps? Or am I still looking at this wrong.

P.S. yes I'm new to this level of programing.... I try to be a quick learning in it all though. So please excuse me if some of my ways of looking at things is way off base... Thats why I'm doing this, to become better. :slight_smile:

If you can display status using 1 or more leds, those will be far faster than serial prints.
A single RGB led and 3 pins can easily display 8 different states. Blinking the led can multiply those 8.

I dunno enough about Wire to help there and it's late and I'm all fuzzy-tired.

Til later, GFS.

Back to beating my head against the wall some more.. Tonight I took the stopwatch to my script. using micros() function this is what I learned.
The time it takes my script to do the IC2 call to the nunchuck to get the user input takes a pretty much constant 11350 microseconds every time.

The time it takes my step motor to move only one single step is 3100 micro seconds.

That has to be the source of my Jitter I have to assume... While in my code at higher speeds, I let my motor make 5 steps before checking for user input again. (which times out to be about 11500 micro seconds)

That tells me that every 11500 micro seconds ( or 3100 if I checked every 1 step) I have to wait 11350 microseconds for the IC2 call to finish before I can get back to telling the motor to make its next step.

2 questions...

  1. is this looking at it correctly?
  2. how do I get around this since I have to keep stepping my motor obviously or it will stop, and I have to go get user input sooner or later.... Or are people just letting there steppers run jerky like this?

After all that work I found the culprit!!!

(sorry if I use some of these next terms incorrectly)
The #included class ArduinoNunchuck.h that links to ArduinoNunchuck.cpp file that I'm was using (I didn't write it). The nunchuck.update function calls ArduinoNunchuck::_SendByte(). Inside that function was a Delay(10).

That Delay(10) is what made my I2C call (nunchuck.update) take 11350 microseconds to run. Removed the Delay(10) and now nunchuck.update takes only 1340 microseconds to run... THAT is small enough I can fit it in between steps.

And in testing, the stepper motor runs just as smooth with or without the user input loop!

Thanks a bunch for marking the thread Solved and for reporting back whatthe problem was! Doing that helps anyone with the same, or even a similar problem.

lar3ry:
Thanks a bunch for marking the thread Solved and for reporting back whatthe problem was! Doing that helps anyone with the same, or even a similar problem.

Its the least I could do.. I have been a lurker here for a long time seeking answers, and I'm finally doing some actual hands on with it now. 4 days of fighting this problem, Not to mention a 6 day hardware issue that ended up being an off the shelf Nunchuck that had a defective Yaxis....
Yup, as it turned out I was doing everything right on day one..... but let me tell you, I learned a lot about i2c data stream and the nunchuck in that fight!

I still believe I could code this better, but for this proof of concept I will call it ok enough.

meliudaj:
After all that work I found the culprit!!!

That Delay(10) is what made my I2C call (nunchuck.update) take 11350 microseconds to run. Removed the Delay(10) and now nunchuck.update takes only 1340 microseconds to run... THAT is small enough I can fit it in between steps.

Do you have the library source? 11350 usecs is 181,600 cycles, what code could take that long to run? Maybe code that waits for some condition of time or response?
It should be possible to make a version of the function in 2 parts, one function for up to the wait that prepares for the next, wait, function. The wait function needs to be written as inline to run faster, it makes a check and if the check is false then it returns to let other tasks run, otherwise it finishes the task.
That's how you unblock. Break up anything you can that waits or runs long and have it run in pieces that don't block and have everything run only on certain generally unique conditions (value in a finite state variable, or time or pin state or interrupt flag, etc) and all the code tasks will generally run as well as they can with the least wasted cycles.

Here is a code play by play..

I #include and initialize my nunchuck class at the start:

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

#define BAUDRATE 19200

ArduinoNunchuk nunchuk = ArduinoNunchuk();

I then get the 8 bytes of Data from the I2C "packet" with this

void loop()
{
  nunchuk.update();
  int analogYraw = nunchuk.analogY;
}

nunchuk.update() looks like this:

void ArduinoNunchuk::update()
{
  int count = 0;
  int values[8];

  Wire.requestFrom(ADDRESS,8);

  while(Wire.available())
  {
    values[count] = Wire.read();
    count++;
  }

 ArduinoNunchuk::analogX = values[0];
 ArduinoNunchuk::analogY = values[1]; 
  ArduinoNunchuk::accelX = (values[2] << 2) | ((values[5] >> 2) & 3);
  ArduinoNunchuk::accelY = (values[3] << 2) | ((values[5] >> 4) & 3);
  ArduinoNunchuk::accelZ = (values[4] << 2) | ((values[5] >> 6) & 3);
  ArduinoNunchuk::zButton = !((values[5] >> 0) & 1);
  ArduinoNunchuk::cButton = !((values[5] >> 1) & 1);

  ArduinoNunchuk::_sendByte(0x00, 0x00);
}

The part that I missed and was biting me in the ass was the very last line. Where it calls _sendByte. ArduinoNunchuck::_sendByte looked like this:

void ArduinoNunchuk::_sendByte(byte data, byte location)
{
  Wire.beginTransmission(ADDRESS);

  Wire.write(location);
  Wire.write(data);

  Wire.endTransmission();

  delay(10);
}

it was the "delay(10);" that was making it process for:

11350 usecs is 181,600 cycles, what code could take that long to run?

after I commented out the (to my knowledge) mostly unneeded Delay(10) it now only takes 1340 usec to run. or ~21500 cycles to run by your calculations..... time wise that seems more reasonable to me.. but no clue on cycles.

My guess is the wire.read() is the cycle hog, due to needing to stay synced to the I2C bus during that operation.. but did not test that part for timings yet.

And for some deeper understanding.. here is the wire.cpp that comes from arduino.. and likely shines a light on whats using up all the time.

/*
  TwoWire.cpp - TWI/I2C library for Wiring & Arduino
  Copyright (c) 2006 Nicholas Zambetti.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
  Modified 2012 by Todd Krein (todd@krein.org) to implement repeated starts
*/

extern "C" {
  #include <stdlib.h>
  #include <string.h>
  #include <inttypes.h>
  #include "twi.h"
}

#include "Wire.h"

// Initialize Class Variables //////////////////////////////////////////////////

uint8_t TwoWire::rxBuffer[BUFFER_LENGTH];
uint8_t TwoWire::rxBufferIndex = 0;
uint8_t TwoWire::rxBufferLength = 0;

uint8_t TwoWire::txAddress = 0;
uint8_t TwoWire::txBuffer[BUFFER_LENGTH];
uint8_t TwoWire::txBufferIndex = 0;
uint8_t TwoWire::txBufferLength = 0;

uint8_t TwoWire::transmitting = 0;
void (*TwoWire::user_onRequest)(void);
void (*TwoWire::user_onReceive)(int);

// Constructors ////////////////////////////////////////////////////////////////

TwoWire::TwoWire()
{
}

// Public Methods //////////////////////////////////////////////////////////////

void TwoWire::begin(void)
{
  rxBufferIndex = 0;
  rxBufferLength = 0;

  txBufferIndex = 0;
  txBufferLength = 0;

  twi_init();
}

void TwoWire::begin(uint8_t address)
{
  twi_setAddress(address);
  twi_attachSlaveTxEvent(onRequestService);
  twi_attachSlaveRxEvent(onReceiveService);
  begin();
}

void TwoWire::begin(int address)
{
  begin((uint8_t)address);
}

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop)
{
  // clamp to buffer length
  if(quantity > BUFFER_LENGTH){
    quantity = BUFFER_LENGTH;
  }
  // perform blocking read into buffer
  uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop);
  // set rx buffer iterator vars
  rxBufferIndex = 0;
  rxBufferLength = read;

  return read;
}

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity)
{
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}

uint8_t TwoWire::requestFrom(int address, int quantity)
{
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}

uint8_t TwoWire::requestFrom(int address, int quantity, int sendStop)
{
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)sendStop);
}

void TwoWire::beginTransmission(uint8_t address)
{
  // indicate that we are transmitting
  transmitting = 1;
  // set address of targeted slave
  txAddress = address;
  // reset tx buffer iterator vars
  txBufferIndex = 0;
  txBufferLength = 0;
}

void TwoWire::beginTransmission(int address)
{
  beginTransmission((uint8_t)address);
}

//
//	Originally, 'endTransmission' was an f(void) function.
//	It has been modified to take one parameter indicating
//	whether or not a STOP should be performed on the bus.
//	Calling endTransmission(false) allows a sketch to 
//	perform a repeated start. 
//
//	WARNING: Nothing in the library keeps track of whether
//	the bus tenure has been properly ended with a STOP. It
//	is very possible to leave the bus in a hung state if
//	no call to endTransmission(true) is made. Some I2C
//	devices will behave oddly if they do not see a STOP.
//
uint8_t TwoWire::endTransmission(uint8_t sendStop)
{
  // transmit buffer (blocking)
  int8_t ret = twi_writeTo(txAddress, txBuffer, txBufferLength, 1, sendStop);
  // reset tx buffer iterator vars
  txBufferIndex = 0;
  txBufferLength = 0;
  // indicate that we are done transmitting
  transmitting = 0;
  return ret;
}

//	This provides backwards compatibility with the original
//	definition, and expected behaviour, of endTransmission
//
uint8_t TwoWire::endTransmission(void)
{
  return endTransmission(true);
}

// must be called in:
// slave tx event callback
// or after beginTransmission(address)
size_t TwoWire::write(uint8_t data)
{
  if(transmitting){
  // in master transmitter mode
    // don't bother if buffer is full
    if(txBufferLength >= BUFFER_LENGTH){
      setWriteError();
      return 0;
    }
    // put byte in tx buffer
    txBuffer[txBufferIndex] = data;
    ++txBufferIndex;
    // update amount in buffer   
    txBufferLength = txBufferIndex;
  }else{
  // in slave send mode
    // reply to master
    twi_transmit(&data, 1);
  }
  return 1;
}

// must be called in:
// slave tx event callback
// or after beginTransmission(address)
size_t TwoWire::write(const uint8_t *data, size_t quantity)
{
  if(transmitting){
  // in master transmitter mode
    for(size_t i = 0; i < quantity; ++i){
      write(data[i]);
    }
  }else{
  // in slave send mode
    // reply to master
    twi_transmit(data, quantity);
  }
  return quantity;
}

// must be called in:
// slave rx event callback
// or after requestFrom(address, numBytes)
int TwoWire::available(void)
{
  return rxBufferLength - rxBufferIndex;
}

// must be called in:
// slave rx event callback
// or after requestFrom(address, numBytes)
int TwoWire::read(void)
{
  int value = -1;
  
  // get each successive byte on each call
  if(rxBufferIndex < rxBufferLength){
    value = rxBuffer[rxBufferIndex];
    ++rxBufferIndex;
  }

  return value;
}

// must be called in:
// slave rx event callback
// or after requestFrom(address, numBytes)
int TwoWire::peek(void)
{
  int value = -1;
  
  if(rxBufferIndex < rxBufferLength){
    value = rxBuffer[rxBufferIndex];
  }

  return value;
}

void TwoWire::flush(void)
{
  // XXX: to be implemented.
}

// behind the scenes function that is called when data is received
void TwoWire::onReceiveService(uint8_t* inBytes, int numBytes)
{
  // don't bother if user hasn't registered a callback
  if(!user_onReceive){
    return;
  }
  // don't bother if rx buffer is in use by a master requestFrom() op
  // i know this drops data, but it allows for slight stupidity
  // meaning, they may not have read all the master requestFrom() data yet
  if(rxBufferIndex < rxBufferLength){
    return;
  }
  // copy twi rx buffer into local read buffer
  // this enables new reads to happen in parallel
  for(uint8_t i = 0; i < numBytes; ++i){
    rxBuffer[i] = inBytes[i];    
  }
  // set rx iterator vars
  rxBufferIndex = 0;
  rxBufferLength = numBytes;
  // alert user program
  user_onReceive(numBytes);
}

// behind the scenes function that is called when data is requested
void TwoWire::onRequestService(void)
{
  // don't bother if user hasn't registered a callback
  if(!user_onRequest){
    return;
  }
  // reset tx buffer iterator vars
  // !!! this will kill any pending pre-master sendTo() activity
  txBufferIndex = 0;
  txBufferLength = 0;
  // alert user program
  user_onRequest();
}

// sets function called on slave write
void TwoWire::onReceive( void (*function)(int) )
{
  user_onReceive = function;
}

// sets function called on slave read
void TwoWire::onRequest( void (*function)(void) )
{
  user_onRequest = function;
}

// Preinstantiate Objects //////////////////////////////////////////////////////

TwoWire Wire = TwoWire();

This code blocks until 8 characters arrive at 19200 after the Nunchuk sends them, first thing.

void ArduinoNunchuk::update()
{
  int count = 0;
  int values[8];

  Wire.requestFrom(ADDRESS,8);

  while(Wire.available())
  {
    values[count] = Wire.read();
    count++;
  }

 ArduinoNunchuk::analogX = values[0];
 ArduinoNunchuk::analogY = values[1];
 ArduinoNunchuk::accelX = values[2];
 ArduinoNunchuk::accelY = values[3];
 ArduinoNunchuk::accelZ = values[4];
 ArduinoNunchuk::zButton = values[5];
    
  ArduinoNunchuk::accelX = (values[2] << 2) | ((values[5] >> 2) & 3);
  ArduinoNunchuk::accelY = (values[3] << 2) | ((values[5] >> 4) & 3);
  ArduinoNunchuk::accelZ = (values[4] << 2) | ((values[5] >> 6) & 3);
  ArduinoNunchuk::zButton = !((values[5] >> 0) & 1);
  ArduinoNunchuk::cButton = !((values[5] >> 1) & 1);

  ArduinoNunchuk::_sendByte(0x00, 0x00);
}

GoForSmoke:
This code blocks until 8 characters arrive at 19200 after the Nunchuk sends them, first thing.

void ArduinoNunchuk::update()

{
  int count = 0;
  int values[8];

Wire.requestFrom(ADDRESS,8);

while(Wire.available())
  {
    values[count] = Wire.read();
    count++;
  }

ArduinoNunchuk::analogX = values[0];
ArduinoNunchuk::analogY = values[1];
  ArduinoNunchuk::accelX = (values[2] << 2) | ((values[5] >> 2) & 3);
  ArduinoNunchuk::accelY = (values[3] << 2) | ((values[5] >> 4) & 3);
  ArduinoNunchuk::accelZ = (values[4] << 2) | ((values[5] >> 6) & 3);
  ArduinoNunchuk::zButton = !((values[5] >> 0) & 1);
  ArduinoNunchuk::cButton = !((values[5] >> 1) & 1);

ArduinoNunchuk::_sendByte(0x00, 0x00);
}

Exactly... I don't see how you get around that. from what I'm seeing wire.read is pulling the data to my variable as fast as the I2C connection can give it. (19200 i believe) and I need all 6 bytes for my data from nunchuck to be any good.

I suppose break the read up into grabbing only one byte at a time and interrupting.??? I'm not sure how exactly I will do that, but will look into it.

P.S. YES it is 6 bytes not 8... not sure why he had 8 in there... changed it to 6 and that brought the time down from 1340 to 1120 microseconds. Getting better! :slight_smile:

meliudaj:

GoForSmoke:
This code blocks until 8 characters arrive at 19200 after the Nunchuk sends them, first thing.

void ArduinoNunchuk::update()

{
  int count = 0;
  int values[8];

Wire.requestFrom(ADDRESS,8);

while(Wire.available())
  {
    values[count] = Wire.read();
    count++;
  }

ArduinoNunchuk::analogX = values[0];
ArduinoNunchuk::analogY = values[1];
  ArduinoNunchuk::accelX = (values[2] << 2) | ((values[5] >> 2) & 3);
  ArduinoNunchuk::accelY = (values[3] << 2) | ((values[5] >> 4) & 3);
  ArduinoNunchuk::accelZ = (values[4] << 2) | ((values[5] >> 6) & 3);
  ArduinoNunchuk::zButton = !((values[5] >> 0) & 1);
  ArduinoNunchuk::cButton = !((values[5] >> 1) & 1);

ArduinoNunchuk::_sendByte(0x00, 0x00);
}

Exactly... I don't see how you get around that. from what I'm seeing wire.read is pulling the data to my variable as fast as the I2C connection can give it. (19200 i believe) and I need all 6 bytes for my data from nunchuck to be any good.

I suppose break the read up into grabbing only one byte at a time and interrupting.??? I'm not sure how exactly I will do that, but will look into it.

P.S. YES it is 6 bytes not 8... not sure why he had 8 in there... changed it to 6 and that brought the time down from 1340 to 1120 microseconds. Getting better! :slight_smile:

The hangup is likely in this function call:
Wire.requestFrom(ADDRESS,8);

The code that follows that expects a filled buffer and works, I'd say that line above is where it hangs.

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop)
{
  // clamp to buffer length
  if(quantity > BUFFER_LENGTH){
    quantity = BUFFER_LENGTH;
  }
  // perform blocking read into buffer
  uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop);
  // set rx buffer iterator vars
  rxBufferIndex = 0;
  rxBufferLength = read;

  return read;
}

Which has this gem, note the comment
// perform blocking read into buffer
uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop);

That code is in twi.c

/* 
 * Function twi_readFrom
 * Desc     attempts to become twi bus master and read a
 *          series of bytes from a device on the bus
 * Input    address: 7bit i2c device address
 *          data: pointer to byte array
 *          length: number of bytes to read into array
 *          sendStop: Boolean indicating whether to send a stop at the end
 * Output   number of bytes read
 */
uint8_t twi_readFrom(uint8_t address, uint8_t* data, uint8_t length, uint8_t sendStop)
{
  uint8_t i;

  // ensure data will fit into buffer
  if(TWI_BUFFER_LENGTH < length){
    return 0;
  }

  // wait until twi is ready, become master receiver
  while(TWI_READY != twi_state){
    continue;
  }
  twi_state = TWI_MRX;
  twi_sendStop = sendStop;
  // reset error state (0xFF.. no error occured)
  twi_error = 0xFF;

  // initialize buffer iteration vars
  twi_masterBufferIndex = 0;
  twi_masterBufferLength = length-1;  // This is not intuitive, read on...
  // On receive, the previously configured ACK/NACK setting is transmitted in
  // response to the received byte before the interrupt is signalled. 
  // Therefor we must actually set NACK when the _next_ to last byte is
  // received, causing that NACK to be sent in response to receiving the last
  // expected byte of data.

  // build sla+w, slave device address + w bit
  twi_slarw = TW_READ;
  twi_slarw |= address << 1;

  if (true == twi_inRepStart) {
    // if we're in the repeated start state, then we've already sent the start,
    // (@@@ we hope), and the TWI statemachine is just waiting for the address byte.
    // We need to remove ourselves from the repeated start state before we enable interrupts,
    // since the ISR is ASYNC, and we could get confused if we hit the ISR before cleaning
    // up. Also, don't enable the START interrupt. There may be one pending from the 
    // repeated start that we sent outselves, and that would really confuse things.
    twi_inRepStart = false;			// remember, we're dealing with an ASYNC ISR
    TWDR = twi_slarw;
    TWCR = _BV(TWINT) | _BV(TWEA) | _BV(TWEN) | _BV(TWIE);	// enable INTs, but not START
  }
  else
    // send start condition
    TWCR = _BV(TWEN) | _BV(TWIE) | _BV(TWEA) | _BV(TWINT) | _BV(TWSTA);

  // wait for read operation to complete
  while(TWI_MRX == twi_state){
    continue;
  }

  if (twi_masterBufferIndex < length)
    length = twi_masterBufferIndex;

  // copy twi buffer to data
  for(i = 0; i < length; ++i){
    data[i] = twi_masterBuffer[i];
  }
	
  return length;
}

I see 2 places it needs to break;

  // wait until twi is ready, become master receiver
  while(TWI_READY != twi_state){
    continue;
  }

and

  // wait for read operation to complete
  while(TWI_MRX == twi_state){
    continue;
  }

There needs to be 3 functions, the first 2 would end just before the wait. The new ones would only run in loop when a state variable has a certain value to keep them from running every time that loop does. You only check for TWO_READY or TWI_MRX when you expect I2C data. So the state value determines if the function is run.

So when it's time in the original twi_readFrom() to wait for TWI_READY the state is set to run the second function. The second function returns immediately if (TWI_READY != twi_state). It doesn't do anything but it doesn't hang around NOT doing anything.

3rd function, same drill about the wait but different if and different actions when it does run.

2nd and 3rd functions should some be written as inline functions if possible.

I would put all post setup() I2C code under 1 state variable and call it twiState. The motor control should have a different state variable and its own switch-case that runs BEFORE the I2C switch-case. that puts the motor independent of the I2C code and at a higher priority. The states of each do not conflict as long as no code blocks or hogs loop(). Whenever the motor runs, I2C runs next, they get their turn.

Thank you! This was incredibly helpful. My library didn't work without the delay, it just ran the stepper in one direction, but with the delay changed from (10) to (1) it significantly increased the speed of my byj48 stepper. Will look more into the .cpp file and see where i can expedite the processing.

Compared to a standard joystick variation, my nunchuk is running at about 20% full speed, but that's up from <5% full speed prior to removing the delay, so getting there.