Transmitting two separate encoder values between two photons using Serial1


#1

Hello!

I am using two optical encoders attached to stepper motors in order to determine the orientation of a rotating object. One photon is being used to receive the encoder data and transmit it to the second photon. The second photon will then process both encoder’s data.

I would like help sending over both sets of encoder step data (integer values from 0 - 500) using the Tx and Rx pins. So far, the receiver photon only receives the encoder values 0 - 255, resets back to 0, and starts again.

My first question is how to send integer step data that is higher than 255. My second question is how to send that data for both encoders using the Serial1 functionality.

Transmitter:

#include "RotaryEncoder.h"
#include "math.h"

SYSTEM_MODE(MANUAL);

#define ENC2_INPUT_A            (A0)
#define ENC2_INPUT_B            (A1)
#define ENC2_INPUT_I            (A2)
#define ENC1_INPUT_A            (D1)
#define ENC1_INPUT_B            (D5)
#define ENC1_INPUT_I            (D6)
#define trigger                 (D7)
#define INDEX_PULSE_TIME_US     (20)

volatile int positionEnc1 = 0, positionEnc2 = 0;

RotaryEncoder rotaryEncoder1(ENC1_INPUT_A, ENC1_INPUT_B);
RotaryEncoder rotaryEncoder2(ENC2_INPUT_A, ENC2_INPUT_B);

void encoder1PhaseISR(void) {
    rotaryEncoder1.process();
}

void encoder2PhaseISR(void) {
    rotaryEncoder2.process();
}

void encoder1IndexISR(void) {
    volatile unsigned long count = micros();
    
    while ((count - micros()) < INDEX_PULSE_TIME_US) {
        if (pinReadFast(ENC1_INPUT_I) == LOW) {
            return;
        }
    }
    // index pulse latched, reset position
    positionEnc1 = 0;
    rotaryEncoder1.resetPosition();
}

void encoder2IndexISR(void) {
    volatile unsigned long count = micros();
    
    while ((count - micros()) < INDEX_PULSE_TIME_US) {
        if (pinReadFast(ENC2_INPUT_I) == LOW) {
            return;
        }
    }
    // index pulse latched, reset position
    positionEnc2 = 0;
    rotaryEncoder2.resetPosition();
}

void setup() {
  
    // encoder1 config
    pinMode(ENC1_INPUT_A, INPUT_PULLUP);
    pinMode(ENC1_INPUT_B, INPUT_PULLUP);
    pinMode(ENC1_INPUT_I, INPUT_PULLUP);
    
    // encoder 2 config
    pinMode(ENC2_INPUT_A, INPUT_PULLUP);
    pinMode(ENC2_INPUT_B, INPUT_PULLUP);
    pinMode(ENC2_INPUT_I, INPUT_PULLUP);
    
    attachInterrupt(ENC1_INPUT_A, encoder1PhaseISR, CHANGE, 0);
    attachInterrupt(ENC1_INPUT_B, encoder1PhaseISR, CHANGE, 0);
    attachInterrupt(ENC1_INPUT_I, encoder1IndexISR, RISING);
        
    attachInterrupt(ENC2_INPUT_A, encoder2PhaseISR, CHANGE, 0);
    attachInterrupt(ENC2_INPUT_B, encoder2PhaseISR, CHANGE, 0);
    attachInterrupt(ENC2_INPUT_I, encoder2IndexISR, RISING);
    
    pinMode(trigger, OUTPUT);
    
    Serial.begin(9600);
    Serial1.begin(9600);
    Particle.connect();
}

void loop() {
    static float value;
    static int i = 0;

    // PRIMARY
    positionEnc1 = rotaryEncoder2.read();
    // For Putty
    Serial.print("Encoder 1:   position cycles: ");
    Serial.print(positionEnc1);
    Serial.print("   deg: ");
    Serial.println((float)positionEnc1/(float)500 * 360.0);
    // For Data Transfer
    Serial1.write(positionEnc1);
    
    // SECONDARY
    positionEnc2 = rotaryEncoder2.read();
    // For Putty
    Serial.print("Encoder 2:   position cycles: ");
    Serial.print(positionEnc2);
    Serial.print("   deg: ");
    Serial.println((float)positionEnc2/(float)500 * 360.0);
    // For Data Transfer
    Serial1.write(positionEnc2);
   
    if (i++ % 1000 == 0) {
        Particle.process();
    }

Receiver:

void setup() {
    Serial.begin(9600);
    Serial1.begin(9600);
    
    Particle.connect();
}

void loop() {
    int value;
    static int i = 0;

    while(Serial1.available())  {
        value = Serial1.read();
        Serial.println(value);
    
        if (i++ % 1000 == 0) {
            Particle.process();
        }
    } 
}

Thanks in advance for any help you can provide!


#2

My guess is the serial print is wrapping the value. Converting the integer to a character byte that only has a range 0 - 255.

Convert the integer to a string and print that. The receiver will need to look for up to three bytes and convert back to an integer.

There are several different ways to go with this depending on the overall design. Will this be the only data you will be sending or will you be adding more things in the future?


#3

This will be the only data I will be sending over. Encoder1 step location (0-500) and Encoder2 step location (0-500). So hopefully two integers converted to a string per loop. How would you convert to a string and have the receiver know to look for up to three bytes and convert back to an integer?


#4

There are several ways to do this; the way I would do it would be something like this. Create a string from the two values with a comma separating the two, and add a terminating character (* in my code below). You need to be using Serial1.print(), not Serial1.write(), which only sends one byte.

In the sender, once you have your 2 values, you can create the string like so,

char buff[9];
snprintf(buff, 9, "%d,%d*", positionEnc1,positionEnc2);
Serial1.print(buff);

This will create a c-string like, 47,312*

In the receiver, you can use readBytesUntil() to find that terminating character, then use strtok() and atoi() to split apart the string, and convert it to two ints,

while(Serial1.available())  {
    char buff[8];
    int bytesRead = Serial1.readBytesUntil('*', buff, 8);
    //Serial.printlnf("bytes read = %d", bytesRead);
    buff[bytesRead] = '\0'; //terminate the buffer with a 0 to make a valid c-string
    //Serial.println(buff);
    int enc1 = atoi(strtok(buff, ",")); // sepaarate the string using the comma, and convert to int
    int enc2 = atoi(strtok(NULL, ","));
    Serial.printlnf("positionEnc1: %d   positionEnc2: %d", enc1, enc2);
} 

#5

Wow, thank you so much for the reply! I shall try this out tomorrow to see how it works with my code/setup. Just to be clear, the buff will be 9 bytes because each encoder value will be 4 bytes each, and the terminator character is one? Additionally, do you have a resource that describes how and when to use the atoi() and strtok() functions? I have never seen those before and would love to learn more.


#6

Not exactly. The numbers from the encoder will be at most 3 bytes each (one for each digit), plus 1 for the comma, 1 for the asterisk, and 1 for the terminal 0. The length of the string that snprintf() creates will be at most one less than the number you pass as the second argument, leaving space for the terminating null character.

There are several websites that are good for learning c-functions. The ones I use most are www.cplusplus.com and https://www.tutorialspoint.com


#7

Thank you very much for all the help and references. Greatly appreciated!


#8

As another follow-up question @Ric, is it possible to not convert each encoder value to a char and instead send the data using serial1.write() with a string of bytes?


#9

I don’t know why you would want to do it that way, but yes, it can be done. You would need to separate each number (or at least those greater than 255) into high and low bytes, and send a separate Serial1.write() for each. So you would do 4 serial writes, two for each of your encoder values. On the receiving end, you would then have to reassemble those numbers to get the original values back. It wouldn’t be that difficult, but I don’t see any advantage to doing it that way.


#10

That makes a lot of sense. Thank you for your help!


#11

I ported the “EasyTransfer Arduino Library” (http://www.billporter.info/2011/05/30/easytransfer-arduino-library/) over to Raspberry PI, Photon, TI EK-TM4C1294XL and LAUNCHXL-F28379D. It gives you a way to serialize data structures on one computer and send them over another computer as binary data with error detection using a serial port, TCP/UDP, I2C, etc. I also use it to control a PhoBot from a Raspberry Pi over a TCP link using a PS3 controller over Bluetooth. If you are interested I can share my code with you. It also takes care of synchronizing the byte stream using CRCs.


#12

As an update, @Ric, your code was able to transfer over the data I needed but I think it was too computationally expensive to run with my processing code. That being said, thank you for all your help, I learned a lot.

@RLRJCN, thank you for your response! I was able to incorporate that library into my code and so far it works flawlessly. Thanks!


#13

What do you mean by that? Is it too slow? If so, what part?


#14

I am using the encoder data to control the motors. Using a stepper motor library, I am using the encoder data to check the rotational position with step counts (this will ensure that the rotations are accurate and will let me know if the motors have skipped steps). When I used your suggestion, the motors could not operate normally within the loop. They would run painfully slow and could not be controlled with my potentiometers. However, after incorporating @RLRJCN’s suggested library, I was able to control the motors as desired. I believe since the photon had to convert the integer values to characters, send them, then convert them back, the code would finish the loop slightly slower than the motors wanted. If I interpreted the library that was suggested correctly, it sends the data in chunks of binary, which is easier for the photon to process.


#15

I don’t think it has much to do with ease of processing, but the binary representation of a number (<= 500) would only take 9 bits, whereas converting to a string of 3 characters would be 24 bits, so probably that’s why the library is faster. BTW, you’re only sending the data at 9600 baud, but the hardware can handle up to 115200 baud. The transmission/reception of the data will be what is taking the biggest chunk of time.


#16

That makes a lot of sense. I may try bumping up the baud rate to quicken the transfer. Thank you!


#18

#19