sending 2 bytes over Xbee

Hey - my question is this:
I'm trying to pass along data from an optical encoder (on a "sender" arduino + Xbee S1) to a stepper motor (connected to a "receiver" arduino + Xbee S1). The data is of course an int which I guess I have to break down into 2 bytes in order to be able to send continually over the Xbees. Only problem is that I can't figure out how to do that and keep the order of 1st byte and 2nd byte. I've been doing some tests with a friend who's much more experienced at this but still it seems to be a problem. I would very much appreciate you thoughts and tips.
Here are the test codes:
Sender:

void setup(){
  Serial.begin(38400);
}

void loop() {
  int someValue = random(0, 32767);
  
  outputBytes("P", someValue);
}



void outputBytes(char* header, int value){
  //translate the value into a header + 2 data bytes
  //Works for numbers upto 32767 / -32767
  //this way we know that the value is always exactly 6 bytes
  //makes it much easier to receive on the other end
  int MSB;
  int LSB;

  MSB = value >> 8; //get just the first byte
  LSB = value & 0b0000000011111111; //get just the second byte

  Serial.print(header);
  Serial.write(MSB);
  Serial.write(LSB);
  Serial.write(LSB);
  Serial.write(MSB);
  Serial.print(header);
}

And receiver:

void setup(){
  Serial.begin(38400);
}

void loop() {
  
  while(Serial.available() > 6){
    byte header = Serial.read(); //read header byte - 80 == P
    
    if(header == 80){
      byte MSB = Serial.read();
      byte LSB = Serial.read();
      byte LSB1 = Serial.read();
      byte MSB1 = Serial.read();
      byte foot = Serial.read();
      
      //make sure it fits the pattern
      
      if(MSB == MSB1 && LSB == LSB1 && header == foot){
        int positionValue = convertBytes(MSB, LSB);
        Serial.print("got a value: ");
        
        Serial.println(positionValue);
      }else{
       Serial.println("that didnt work"); 
       
       Serial.print("MSB: ");
       Serial.println(MSB);
       
       Serial.print("LSB: ");
       Serial.println(LSB);
       
       Serial.print("LSB1: ");
       Serial.println(LSB1);
       
       Serial.print("MSB1: ");
       Serial.println(MSB1);
       
       Serial.print("foot: ");
       Serial.println(foot);
       
       Serial.println("/////////////////");
      }
      
    }else{
              
       Serial.print("HEADER WAS NOT A P");
              
       Serial.print("header: ");
       Serial.println(header);
       
       Serial.println("/////////////////");
      
    }
    
  }
  
}



int convertBytes(int MSB, int LSB){  
    //99% sure this is right
    return(MSB << 8) | LSB; //get just the first byte
}

void doMotorThing(int positionValue){
  //do something with the positionValue
  
}

Only problem is that I can't figure out how to do that and keep the order of 1st byte and 2nd byte.

Serial data is not guaranteed to be sent or received, especially when XBees are involved. You should not be trying to send just two bytes of binary data. You need some kind of sync byte, so you know when there has been data loss. It's far better, though, to send something like "<3:560>" where there is a start of packet marker (<), a length value (3), a separator (:), an actual value (560), and an end of packet marker (>).

Read and store data between the start and end of packet markers. Then, check that the stored data contains a separator. If so, parse the string to get the two tokens ("3" and "560"). Convert the first token to an int (atoi()), and make sure that the second token length is that long. If so, convert the second token to an int, and use that value for the stepper motor (number of steps?).

Hey PaulS - thanks.
Since this is way over my head and I will take my time figuring out what you meant, I'll just ask for one clarification: Will this method you propose allow the continuous flow of data from encoder to stepper? Meaning - since the whole set up is meant to enable the synced control of an encoder over the position a stepper I cannot send "packets" of value changes and than wait. The "wired" version of the project moves a camera lens in sync with the encoder.
(Please remember I'm a novice at this so if my question indicates I understood you poorly try and be patient... :disappointed_relieved:)

Will this method you propose allow the continuous flow of data from encoder to stepper?

Yes, although it won't be as fast, requiring more processing on each end.

Meaning - since the whole set up is meant to enable the synced control of an encoder over the position a stepper I cannot send "packets" of value changes and than wait. The "wired" version of the project moves a camera lens in sync with the encoder.

I think, then, that sending an int is the wrong thing to be doing. It seems that the encoder end needs to send a "I saw a step happen" message that the stepper end receives, to know when to step again. That could be encoded in a byte, instead of an int.

Sending one byte packets means that you never can get out of sync.

I think, then, that sending an int is the wrong thing to be doing. It seems that the encoder end needs to send a "I saw a step happen" message that the stepper end receives, to know when to step again. That could be encoded in a byte, instead of an int.

This indeed seems a smarter solution.
Thanks - I'll try and work this out.

Back again with some questions :slight_smile:

I've worked on the basic code and have split it into 2 sides - Send and Receive.
In the Send code I've intentionally left the motor related code to make sure It's working as intended -I double check myself by going wireless and wired.

2 things are wrong though:

  1. The ratio between the encoder and stepper seem to have changed when going wireless - less stepper rotation in relation to same encoder rotation.
  2. Stepped direction is not changing even when encoder value goes back...

What am I missing here?

Sender Code:

// Movement of Optical encoder is translated to moving stepper


#include <AccelStepper.h>

//encoder/motor/driver setup
int easyDriverMicroSteps = 4; //I think this is 4 or 8
int rotaryEncoderSteps = 75; //setps per rev on your encoder
int motorStepsPerRev = 200; //this is right  - steps per rev on the motor

int MinPulseWidth = 50; //too low and the motor will stall, too high and it will slow it down

//these pins can not be changed 2/3 are special pins
int encoderPin1 = 2;
int encoderPin2 = 3;

int easyDriverStepPin = 4;
int easyDriverDirPin = 5;

volatile int lastEncoded = 0;
volatile long encoderValue = 0;

long lastencoderValue = 0;
int lastMSB = 0;
int lastLSB = 0;

//1 means we are using a motor driver with the library
AccelStepper stepper(1, easyDriverStepPin, easyDriverDirPin);


void setup(){
  Serial.begin(115200);

  stepper.setMinPulseWidth(MinPulseWidth); //may need to play with this, but I think this is good.
  stepper.setMaxSpeed(50000);
  stepper.setAcceleration(1000000000); //try 100, or 1000 and see if it works
  stepper.setSpeed(50000); //play with this to see if it makes a diff (1 to 1000)


  pinMode(encoderPin1, INPUT);
  pinMode(encoderPin2, INPUT);

  digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
  digitalWrite(encoderPin2, HIGH); //turn pullup resistor on

  //call updateEncoder() when any high/low changed seen
  //on interrupt 0 (pin 2), or interrupt 1 (pin 3) 
  attachInterrupt(0, updateEncoder, CHANGE);
  attachInterrupt(1, updateEncoder, CHANGE);
}



void loop(){
  //go to where the encoder was turned to.
  int stepsPerRotaryStep = (motorStepsPerRev * easyDriverMicroSteps) / rotaryEncoderSteps;
  stepper.moveTo(encoderValue * stepsPerRotaryStep);
  stepper.run();
  
}


void updateEncoder(){
  int MSB = digitalRead(encoderPin1); //MSB = most significant bit
  int LSB = digitalRead(encoderPin2); //LSB = least significant bit

  int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)
   {
    encoderValue ++;
    Serial.write(1);
   }
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) 
    {
    encoderValue --;
    Serial.write (2);
    }

  lastEncoded = encoded; //store this value for next time
  //Serial.println(encoderValue);
}

Receiver Code

// Movement of Optical encoder is translated to moving stepper


#include <AccelStepper.h>

//encoder/motor/driver setup
int easyDriverMicroSteps = 8; //I think this is 4 or 8
int rotaryEncoderSteps = 75; //setps per rev on your encoder
int motorStepsPerRev = 200; //this is right  - steps per rev on the motor

int MinPulseWidth = 50; //too low and the motor will stall, too high and it will slow it down

int easyDriverStepPin = 4;
int easyDriverDirPin = 5;
int enablePin = 6;

volatile int lastEncoded = 0;
volatile long encoderValue = 0;
int dataReceive = 0;
long lastencoderValue = 0;


//1 means we are using a motor driver with the library
AccelStepper stepper(1, easyDriverStepPin, easyDriverDirPin);


void setup(){
  Serial.begin(115200);

  stepper.setMinPulseWidth(MinPulseWidth); //may need to play with this, but I think this is good.
  stepper.setMaxSpeed(50000);
  stepper.setAcceleration(1000000000); //try 100, or 1000 and see if it works
  stepper.setSpeed(50000); //play with this to see if it makes a diff (1 to 1000)
  
  pinMode(enablePin, OUTPUT);
 }

void loop(){
  //go to where the encoder was turned to.
  if (Serial.available())
  {
    dataReceive = Serial.read();
    if (dataReceive == 1)
      encoderValue ++;
    else if (dataReceive == 2)
      encoderValue --;
      
  
  int stepsPerRotaryStep = (motorStepsPerRev * easyDriverMicroSteps) / rotaryEncoderSteps;
  stepper.moveTo(encoderValue * stepsPerRotaryStep);
  stepper.run();
  }
}

PaulS, any chance of you looking at this?
Thanks.

  stepper.setMaxSpeed(50000);
  stepper.setAcceleration(1000000000); //try 100, or 1000 and see if it works

The setMaxSpeed method has a comment that says that more than 1000 steps per second is unreliable. That value is slightly over the reliable limit.

The set Accelleration method defines the maximum acceleration in steps per second per second. That value is ridiculous.

You can't do Serial I/O in an ISR.

Why is the value of easyDriverMicroSteps different on the two Arduinos?

You are correct of course but this is due to some testing and not the values in my sketch - I mistakenly left it there - - - :blush:

Same goes for the difference in ED microsteps - the value is defined as 1 after much tests to have a good response "feel" to it.

What does "you can't do Serial I/O in an ISR" mean?
Serial IO is obvious, unlike "ISR"...

ISR = Interrupt Service Routine. It refers to any function that is called by an interrupt. You are using attachInterrupt to define a function to be called when an interrupt occurs. When that function is called, other interrupts are disabled. Sending serial data relies on interrupts being enabled, which is not the case in your interrupt service routine. So, you can't call Serial.print() or Serial.write() from an ISR.

Ok, so my option here is to use the optical encoder w/o interrupts (is that possible?) thus enabling Serial.write()?
Or is there anything else I can do to read from encoder and sent the data via Xbee?
(thank you once again - this is new stuff to me and very interesting)

You can only read encoder data, if it changes very rapidly, using interrupts. Leave that code alone. Send serial data in loop(), if there is anything to send. Determine that by setting a flag in the ISR, and checking it in loop. It looks, though, like you want to send a sync byte every time the encoder changes values. Depending on how often that happens, that may not be a realistic thing to do.

Ok, think I got your meaning.
Thank you very much.

Just to update you guys, and point to the solution in case someone needs it -
It's much simpler, and it's got nothing to do with the interrupts (at least not in the simple sense).
The thing is the stepper.run() function which had to move outside of the if loop - this way the stepper is constantly updated.

So instead of

void loop () {
 if (Serial.available())
  {
    dataReceive = Serial.read();
    if (dataReceive == 1)
      encoderValue ++;
    else if (dataReceive == 2)
      encoderValue --;
      
  int stepsPerRotaryStep = (motorStepsPerRev * easyDriverMicroSteps) / rotaryEncoderSteps;
  stepper.moveTo(encoderValue * stepsPerRotaryStep);
  stepper.run();
}
  }

It's

void loop(){
  stepper.run();
  if (Serial.available())
  {
    dataReceive = Serial.read();
    if (dataReceive == 1)
      encoderValue ++;
    else if (dataReceive == 2)
      encoderValue --;
    
  int stepsPerRotaryStep = (motorStepsPerRev * easyDriverMicroSteps) / rotaryEncoderSteps;
  stepper.moveTo(encoderValue * stepsPerRotaryStep);
   }
}

Hope this'll help others. :slight_smile: