Rotary Encoder Reciving not working

I am trying to connect a rotary encoder to an arduino and pull data as it turns. I am using the AMT212C-V that connects through a RS485 transceiver (Sparkfun Electronics BOB-10124) to the arduino board. I use the sample code from AMT21 Arduino RS-485 Sample Code | Same Sky to run it all.

I've tried to follow the instructions, but I continue to receive more bytes than the code is expecting. The system expects 2 bytes but claims to receive >30 and when I have output the extra it looks like a bunch of random bits.

I new to using arduino, so I am not sure what to do next to try and fix this.

You should show your code and wiring diagram.

2 Likes

Putting in gear 1 in learning coding is adviced. Using unknown code will always fail and give headache.

1 Like

@xfpd Yes, sorry I forgot to do so.

/*
 * AMT21_Multiturn_RS485_Sample_Code_Mega.ino
 * Company: CUI Devices
 * Author: Jason Kelly, Damon Tarry
 * Version: 1.0.0.0
 * Date: June 29, 2023
 *
 * This sample code can be used with the Arduino Mega to control the AMT21 encoder.
 * It uses one UART to control the encoder and the the another UART to report back to the PC
 * via the Arduino Serial Monitor.
 * For more information or assistance contact CUI Devices for support.
 *
 * After uploading code to Arduino Mega 2560 open the open the Serial Monitor under the Tools
 * menu and set the baud rate to 115200 to view the serial stream the position from the AMT21.
 *
 * This code is compatible with most Arduino boards, but it is important to verify the board
 * to be used has an available serial port for the RS485 connection.
 *
 * Arduino Pin Connections
 * TX:          Pin D19
 * RX:          Pin D18
 * REn:         Pin D8
 * DE:          Pin D9
 *
 *
 * AMT21 Pin Connections
 * Vdd (5V):    Pin  1
 * B:           Pin  2
 * A:           Pin  3
 * GND:         Pin  4
 * NC:          Pin  5
 * NC:          Pin  6
 *
 *
 * Required Equipment Note:
 * The AMT21 requires a high speed RS485 transceiver for operation. At the time this sample
 * code was written Arduino does not sell any compatible RS485 transceivers. For this demo
 * the MAX485 RS485 transceiver was used in a DIP format along with a breadboard. This code was
 * made in conjunction with a walkthrough demo project available online at www.cuidevices.com/resources/.
 *
 * This is free and unencumbered software released into the public domain.
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/* Serial rates for UART */
#define BAUDRATE             115200
#define RS485_BAUDRATE       2000000

/* We will use this define macro so we can write code once compatible with 12 or 14 bit encoders */
#define RESOLUTION            12

/* The AMT21 encoder is able to have a range of different values for its node address. This allows there
 * to be multiple encoders on on the RS485 bus. The encoder will listen for its node address, and that node
 * address doubles as the position request command. This simplifies the protocol so that all the host needs
 * to do is transmit the node address and the encoder will immediately respond with the position. The node address
 * can be any 8-bit number, where the bottom 2 bits are both 0. This means that there are 63 available addresses.
 * The bottom two bits are left as zero, because those bit slots are used to indicate extended commands. Taking
 * the node address and adding 0x01 changes the command to reading the turns counter (multi-turn only), and adding
 * 0x02 indicates that a second extended command will follow the first. We will define two encoder addresses below,
 * and then we will define the modifiers, but to reduce code complexity and the number of defines, we will not
 * define every single variation. 0x03 is unused and therefore reserved at this time.
 *
 */
#define RS485_RESET           0x75
#define RS485_ZERO            0x5E
#define RS485_ENC0            0x54
#define RS485_ENC1            0x58
#define RS485_POS             0x00 //this is unnecessary to use but it helps visualize the process for using the other modifiers
#define RS485_TURNS           0x01
#define RS485_EXT             0x02

/* The RS485 transceiver uses 4 pins to control the state of the differential RS485 lines A/B. To control
 * the transevier we will put those pins on our digital IO pins. We will define those pins here now. We will get
 * into more information about this later on, but it is important to understand that because of the high speed of the
 * AMT21 encoder, the toggling of these pins must occur very quickly. More information available in the walkthrough online.
 * Receive enable, drive enable, data in, received data out. Typically the RE and DE pins are connected together and controlled
 * with 1 IO pin but we'll connect them all up for full control.
 */
#define RS485_T_RE            8
#define RS485_T_DE            8
#define RS485_T_DI            18
#define RS485_T_RO            19

/* For ease of reading, we will create a helper function to set the mode of the transceiver. We will send that funciton
 * these arguments corresponding with the mode we want.
 */
#define RS485_T_TX            0 //transmit: receiver off, driver on
#define RS485_T_RX            1 //receiver: driver off, transmit on

void setup()
{
  //Set the modes for the RS485 transceiver
  pinMode(RS485_T_RE, OUTPUT);
  pinMode(RS485_T_DE, OUTPUT);
  //pinMode(RS485_T_DI, OUTPUT);
  //pinMode(RS485_T_RO, OUTPUT);

  //Initialize the UART serial connection to the PC
  Serial.begin(BAUDRATE);

  //Initialize the UART link to the RS485 transceiver
  Serial1.begin(RS485_BAUDRATE);
}

void loop()
{
  //create an array of encoder addresses so we can use them in a loop
  uint8_t addresses[1] = {RS485_ENC0};
  //uint8_t addresses[2] = {RS485_ENC0, RS485_ENC1};

  for(int encoder = 0; encoder < sizeof(addresses); ++encoder)
  {

    //first we will read the position
    setStateRS485(RS485_T_TX); //put the transciver into transmit mode
    //delayMicroseconds(5);   //IO operations take time, let's throw in an arbitrary 10 microsecond delay to make sure the transeiver is ready

    //send the command to get position. All we have to do is send the node address, but we can use the modifier for consistency
    Serial1.write(addresses[encoder] + RS485_POS);

    //We expect a response from the encoder to begin within 3 microseconds. Each byte sent has a start and stop bit, so each 8-bit byte transmits
    //10 bits total. So for the AMT21 operating at 2 Mbps, transmitting the full 20 bit response will take about 10 uS. We expect the response
    //to start after 3 uS totalling 13 microseconds from the time we've finished sending data.
    //So we need to put the transceiver into receive mode within 3 uS, but we want to make sure the data has been fully transmitted before we
    //do that or we could cut it off mid transmission. This code has been tested and optimized for this; porting this code to another device must
    //take all this timing into account.

    //Here we will make sure the data has been transmitted and then toggle the pins for the transceiver
    //Here we are accessing the avr library to make sure this happens very fast. We could use Serial.flush() which waits for the output to complete
    //but it takes about 2 microseconds, which gets pretty close to our 3 microsecond window. Instead we want to wait until the serial transmit flag USCR1A completes.
    while (!(UCSR1A & _BV(TXC1)));

    setStateRS485(RS485_T_RX); //set the transceiver back into receive mode for the encoder response

    //We need to give the encoder enough time to respond, but not too long. In a tightly controlled application we would want to use a timeout counter
    //to make sure we don't have any issues, but for this demonstration we will just have an arbitrary delay before checking to see if we have data to read.
    //delayMicroseconds(15);

    //Response from encoder should be exactly 2 bytes
    int bytes_received = Serial1.available();
    if (bytes_received == 2)
    {
      uint16_t currentPosition = Serial1.read(); //low byte comes first
      currentPosition |= Serial1.read() << 8;    //high byte next, OR it into our 16 bit holder but get the high bit into the proper placeholder

      if (verifyChecksumRS485(currentPosition))
      {
        //we got back a good position, so just mask away the checkbits
        currentPosition &= 0x3FFF;

        //If the resolution is 12-bits, then shift position
        if (RESOLUTION == 12)
        {
          currentPosition = currentPosition >> 2;
        }
        Serial.print("Encoder #");
        Serial.print(encoder, DEC);
        Serial.print(" position: ");
        Serial.println(currentPosition, DEC); //print the position in decimal format
      }
      else
      {
        Serial.print("Encoder #");
        Serial.print(encoder, DEC);
        Serial.print(" position error: Invalid checksum. Given is: ");
        Serial.println(currentPosition, BIN);
      }
    }
    else
    {
      Serial.print("Encoder #");
      Serial.print(encoder, DEC);
      Serial.print(" error reading position: Expected to receive 2 bytes. Actually received ");
      Serial.print(bytes_received, DEC);
      Serial.println(" bytes.");
    }

    //wait briefly before reading the turns counter
    delayMicroseconds(100);

    setStateRS485(RS485_T_TX); //put the transciver into transmit mode
    delayMicroseconds(10);   //IO operations take time, let's throw in an arbitrary 10 microsecond delay to make sure the transeiver is ready

    //send the command to get position. All we have to do is send the node address, but we can use the modifier for consistency
    Serial1.write(addresses[encoder] | RS485_TURNS);

    //wait for command to finish transmitting
    while (!(UCSR1A & _BV(TXC1)));

    setStateRS485(RS485_T_RX); //set the transceiver back into receive mode for the encoder response

    //We need to give the encoder enough time to respond, but not too long. In a tightly controlled application we would want to use a timeout counter
    //to make sure we don't have any issues, but for this demonstration we will just have an arbitrary delay before checking to see if we have data to read.
    delayMicroseconds(30);

    //Response from encoder should be exactly 2 bytes
    bytes_received = Serial1.available();
    if (bytes_received == 2)
    {
      uint16_t currentTurns = Serial1.read(); //low byte comes first
      currentTurns |= Serial1.read() << 8;    //high byte next, OR it into our 16 bit holder but get the high bit into the proper placeholder

      if (verifyChecksumRS485(currentTurns))
      {
        //we got back a good position, so just mask away the checkbits
        currentTurns &= 0x3FFF;

        Serial.print("Encoder #");
        Serial.print(encoder, DEC);
        Serial.print(" turns: ");
        Serial.println(currentTurns, DEC); //print the position in decimal format
      }
      else
      {
        Serial.print("Encoder #");
        Serial.print(encoder, DEC);
        Serial.print(" turns error: Invalid checksum. Given is: ");
        Serial.println(currentTurns, BIN);
      }
    }
    else
    {
      Serial.print("Encoder #");
      Serial.print(encoder, DEC);
      Serial.print(" error reading turns: Expected to receive 2 bytes. Actually received ");
      Serial.print(bytes_received, DEC);
      Serial.println(" bytes.");
    }
  }

  //flush the received serial buffer just in case anything extra got in there
  while (Serial1.available()) Serial1.read();

  //For the purpose of this demo we don't need the position returned that quickly so let's wait a half second between reads
  //delay() is in milliseconds
  delay(500);
}

bool verifyChecksumRS485(uint16_t message)
{
  //using the equation on the datasheet we can calculate the checksums and then make sure they match what the encoder sent
  //checksum is invert of XOR of bits, so start with 0b11, so things end up inverted
  uint16_t checksum = 0x3;
  for(int i = 0; i < 14; i += 2)
  {
    checksum ^= (message >> i) & 0x3;
  }
  return checksum == (message >> 14);
}
/*
 * This function sets the state of the RS485 transceiver. We send it that state we want. Recall above I mentioned how we need to do this as quickly
 * as possible. To be fast, we are not using the digitalWrite functions but instead will access the avr io directly. I have shown the direct access
 * method and left commented the digitalWrite method.
 */
void setStateRS485(uint8_t state)
{
  //switch case to find the mode we want
  switch (state)
  {
    case RS485_T_TX:
      PORTH |= 0b01100000;
      //digitalWrite(RS485_RE, HIGH); //ph5
      //digitalWrite(RS485_DE, HIGH); //ph6
      break;
    case RS485_T_RX:
      PORTH &= 0b10011111;
      //digitalWrite(RS485_RE, LOW);
      //digitalWrite(RS485_DE, LOW);
      break;
    default:
      PORTH &= 0b10111111;
      PORTH |= 0b00100000;
      //digitalWrite(RS485_RE, HIGH);
      //digitalWrite(RS485_DE, LOW);
      break;
  }
}

Hi, @a_h_razz
Welcome to the forum.

What model Arduino are you using?

Thanks.. Tom.... :smiley: :+1: :coffee: :australia:

Looks like code and drawing is for Mega2560 using TXD1/R1 for comms.

The specific model I am using is the ELEGOO MEGA R3 Board ATmega 2560. It's nearly identical to the arduino mega 2560 as far as I am aware. Though, due to my lack of experience with arduino, I do not know what differences I would look for and how I would look for their impact.

Can you make each device work on its own (and save the combination for later)?

I believe I could theoretically. The transceiver's main purpose (to my understanding) is to facilitate the RS-485 communication between the encoder and the arduino to remove noise via the 2 channels A and B. I don't know what the G does - though I assume its ground but then I wonder why the breakout board would have GND and G for ground.

I would have to figure out how the noise cancellation works in place of the encoder and either run it through the transceiver or just do it manually.

Do you think that's the optimal fix?

Make the "> 30" show in the Serial Monitor and (hopefully you can) copy them to paste in the forum.

For some reason, I am no longer getting >30 bytes, but I am still not getting the expected output. Instead its either 1, 3, or 4 bytes instead. However, I don't really understand how I can output the bytes I am receiving. Is there a simple tutorial for such a task because I haven't found one that seems like its for beginners in Arduino.

The same way you saw the ">30"

Your drawing has TX going to TX, RX going to RX, both RX and TX arrows are either RXing or TXing, when one arrow should show RX and the other should show TX. Is your Arduino the DTE? It looks like your XCVR (the DCE) is asserting RTS on the DTE, when it is the DTE that tells the DCE that the DTE (Arduino) wishes to transmit.

Thanks again for the help.

The Arduino is supposed to be the DTE, the XCVR is supposed to be the DCE. I reveresed the RX and TX wires. I had thought RX went to RX and TX went to TX, so that was one mistake (of many :P).

I figured out how to get the binary data to be shwon and I have copied it below.

13:24:45.132 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 1011010,1,1000,
13:24:46.162 -> Encoder #0 error reading position: Expected to receive 2 bytes. Actually received 1 bytes. As binary: 0,
13:24:46.162 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 11010,1,100,
13:24:47.183 -> Encoder #0 error reading position: Expected to receive 2 bytes. Actually received 1 bytes. As binary: 0,
13:24:47.183 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 4 bytes. In Binary: 11010011,11111111,1,11000000,
13:24:48.176 -> Encoder #0 error reading position: Expected to receive 2 bytes. Actually received 1 bytes. As binary: 0,
13:24:48.176 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 11010011,1,100,
13:24:49.188 -> Encoder #0 error reading position: Expected to receive 2 bytes. Actually received 1 bytes. As binary: 0,
13:24:49.188 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 11010011,1,11000000,
13:24:50.219 -> Encoder #0 error reading position: Expected to receive 2 bytes. Actually received 1 bytes. As binary: 0,
13:24:50.219 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 11010011,1,100,

Thinking it was a time delay problem, I added a 15 micro second delay. Now it will sometimes output the position data for the rotary encoder but often switches between the incorrect number of bytes error and a checksum error (which is new).

Here is a new snapshot of the errors (note the position value changes because I was turning the encoder to see what happened):

13:37:34.223 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 1,100,11111111,
13:37:35.199 -> Encoder #0 position: 1856
13:37:35.199 -> Encoder #0 turns error: Invalid checksum. Given is: 1111111111111111
13:37:36.216 -> Encoder #0 position: 1856
13:37:36.216 -> Encoder #0 turns error: Invalid checksum. Given is: 1111111111111111
13:37:37.206 -> Encoder #0 position error: Invalid checksum. Given is: 11001000000000
13:37:37.239 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 0,100,11111111,
13:37:38.214 -> Encoder #0 position error: Invalid checksum. Given is: 1100011100000000
13:37:38.214 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 1 bytes. In Binary: 0,
13:37:39.243 -> Encoder #0 position: 3712
13:37:39.243 -> Encoder #0 turns error: Invalid checksum. Given is: 1111111111111111
13:37:40.221 -> Encoder #0 position error: Invalid checksum. Given is: 100011100000100
13:37:40.257 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 1,1000,11111111,
13:37:41.229 -> Encoder #0 position: 3777
13:37:41.229 -> Encoder #0 turns error: Invalid checksum. Given is: 1111111111111111
13:37:42.261 -> Encoder #0 position: 3777
13:37:42.261 -> Encoder #0 turns error: Invalid checksum. Given is: 1111111111111111
13:37:43.253 -> Encoder #0 position: 3777
13:37:43.253 -> Encoder #0 turns error: Invalid checksum. Given is: 1111111111111111
13:37:44.234 -> Encoder #0 position error: Invalid checksum. Given is: 100011100000100
13:37:44.234 -> Encoder #0 error reading turns: Expected to receive 2 bytes. Actually received 3 bytes. In Binary: 1,100,11111111

Here is my updated code

/*
 * AMT21_Multiturn_RS485_Sample_Code_Mega.ino
 * Company: CUI Devices
 * Author: Jason Kelly, Damon Tarry
 * Version: 1.0.0.0
 * Date: June 29, 2023
 *
 * This sample code can be used with the Arduino Mega to control the AMT21 encoder.
 * It uses one UART to control the encoder and the the another UART to report back to the PC
 * via the Arduino Serial Monitor.
 * For more information or assistance contact CUI Devices for support.
 *
 * After uploading code to Arduino Mega 2560 open the open the Serial Monitor under the Tools
 * menu and set the baud rate to 115200 to view the serial stream the position from the AMT21.
 *
 * This code is compatible with most Arduino boards, but it is important to verify the board
 * to be used has an available serial port for the RS485 connection.
 *
 * Arduino Pin Connections
 * TX:          Pin D19
 * RX:          Pin D18
 * REn:         Pin D8
 * DE:          Pin D9
 *
 *
 * AMT21 Pin Connections
 * Vdd (5V):    Pin  1
 * B:           Pin  2
 * A:           Pin  3
 * GND:         Pin  4
 * NC:          Pin  5
 * NC:          Pin  6
 *
 *
 * Required Equipment Note:
 * The AMT21 requires a high speed RS485 transceiver for operation. At the time this sample
 * code was written Arduino does not sell any compatible RS485 transceivers. For this demo
 * the MAX485 RS485 transceiver was used in a DIP format along with a breadboard. This code was
 * made in conjunction with a walkthrough demo project available online at www.cuidevices.com/resources/.
 *
 * This is free and unencumbered software released into the public domain.
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/* Serial rates for UART */
#define BAUDRATE             115200
#define RS485_BAUDRATE       2000000

/* We will use this define macro so we can write code once compatible with 12 or 14 bit encoders */
#define RESOLUTION            12

/* The AMT21 encoder is able to have a range of different values for its node address. This allows there
 * to be multiple encoders on on the RS485 bus. The encoder will listen for its node address, and that node
 * address doubles as the position request command. This simplifies the protocol so that all the host needs
 * to do is transmit the node address and the encoder will immediately respond with the position. The node address
 * can be any 8-bit number, where the bottom 2 bits are both 0. This means that there are 63 available addresses.
 * The bottom two bits are left as zero, because those bit slots are used to indicate extended commands. Taking
 * the node address and adding 0x01 changes the command to reading the turns counter (multi-turn only), and adding
 * 0x02 indicates that a second extended command will follow the first. We will define two encoder addresses below,
 * and then we will define the modifiers, but to reduce code complexity and the number of defines, we will not
 * define every single variation. 0x03 is unused and therefore reserved at this time.
 *
 */
#define RS485_RESET           0x75
#define RS485_ZERO            0x5E
#define RS485_ENC0            0x54
#define RS485_ENC1            0x58
#define RS485_POS             0x00 //this is unnecessary to use but it helps visualize the process for using the other modifiers
#define RS485_TURNS           0x01
#define RS485_EXT             0x02

/* The RS485 transceiver uses 4 pins to control the state of the differential RS485 lines A/B. To control
 * the transevier we will put those pins on our digital IO pins. We will define those pins here now. We will get
 * into more information about this later on, but it is important to understand that because of the high speed of the
 * AMT21 encoder, the toggling of these pins must occur very quickly. More information available in the walkthrough online.
 * Receive enable, drive enable, data in, received data out. Typically the RE and DE pins are connected together and controlled
 * with 1 IO pin but we'll connect them all up for full control.
 */
#define RS485_T_RE            8
#define RS485_T_DE            8
#define RS485_T_DI            18
#define RS485_T_RO            19

/* For ease of reading, we will create a helper function to set the mode of the transceiver. We will send that funciton
 * these arguments corresponding with the mode we want.
 */
#define RS485_T_TX            0 //transmit: receiver off, driver on
#define RS485_T_RX            1 //receiver: driver off, transmit on

void setup()
{
  //Set the modes for the RS485 transceiver
  pinMode(RS485_T_RE, OUTPUT);
  pinMode(RS485_T_DE, OUTPUT);
  //pinMode(RS485_T_DI, OUTPUT);
  //pinMode(RS485_T_RO, OUTPUT);

  //Initialize the UART serial connection to the PC
  Serial.begin(BAUDRATE);

  //Initialize the UART link to the RS485 transceiver
  Serial1.begin(RS485_BAUDRATE);
}

void loop()
{
  //create an array of encoder addresses so we can use them in a loop
  uint8_t addresses[1] = {RS485_ENC0};
  //uint8_t addresses[2] = {RS485_ENC0, RS485_ENC1};

  for(int encoder = 0; encoder < sizeof(addresses); ++encoder)
  {

    //first we will read the position
    setStateRS485(RS485_T_TX); //put the transciver into transmit mode
    //delayMicroseconds(5);   //IO operations take time, let's throw in an arbitrary 10 microsecond delay to make sure the transeiver is ready

    //send the command to get position. All we have to do is send the node address, but we can use the modifier for consistency
    Serial1.write(addresses[encoder] + RS485_POS);

    //We expect a response from the encoder to begin within 3 microseconds. Each byte sent has a start and stop bit, so each 8-bit byte transmits
    //10 bits total. So for the AMT21 operating at 2 Mbps, transmitting the full 20 bit response will take about 10 uS. We expect the response
    //to start after 3 uS totalling 13 microseconds from the time we've finished sending data.
    //So we need to put the transceiver into receive mode within 3 uS, but we want to make sure the data has been fully transmitted before we
    //do that or we could cut it off mid transmission. This code has been tested and optimized for this; porting this code to another device must
    //take all this timing into account.

    //Here we will make sure the data has been transmitted and then toggle the pins for the transceiver
    //Here we are accessing the avr library to make sure this happens very fast. We could use Serial.flush() which waits for the output to complete
    //but it takes about 2 microseconds, which gets pretty close to our 3 microsecond window. Instead we want to wait until the serial transmit flag USCR1A completes.
    while (!(UCSR1A & _BV(TXC1)));

    setStateRS485(RS485_T_RX); //set the transceiver back into receive mode for the encoder response

    //We need to give the encoder enough time to respond, but not too long. In a tightly controlled application we would want to use a timeout counter
    //to make sure we don't have any issues, but for this demonstration we will just have an arbitrary delay before checking to see if we have data to read.
    delayMicroseconds(15);

    //Response from encoder should be exactly 2 bytes
    int bytes_received = Serial1.available();
    byte bytes_pulled[bytes_received];
    for(int i = 0; i < bytes_received; i++){
      bytes_pulled[i] = Serial1.read();
    }

    if (bytes_received == 2)
    {
      uint16_t currentPosition = bytes_pulled[0]; //low byte comes first
      currentPosition |= bytes_pulled[1] << 8;    //high byte next, OR it into our 16 bit holder but get the high bit into the proper placeholder

      if (verifyChecksumRS485(currentPosition))
      {
        //we got back a good position, so just mask away the checkbits
        currentPosition &= 0x3FFF;

        //If the resolution is 12-bits, then shift position
        if (RESOLUTION == 12)
        {
          currentPosition = currentPosition >> 2;
        }
        Serial.print("Encoder #");
        Serial.print(encoder, DEC);
        Serial.print(" position: ");
        Serial.println(currentPosition, DEC); //print the position in decimal format
      }
      else
      {
        Serial.print("Encoder #");
        Serial.print(encoder, DEC);
        Serial.print(" position error: Invalid checksum. Given is: ");
        Serial.println(currentPosition, BIN);
      }
    }
    else
    {
      Serial.print("Encoder #");
      Serial.print(encoder, DEC);
      Serial.print(" error reading position: Expected to receive 2 bytes. Actually received ");
      Serial.print(bytes_received, DEC);
      Serial.print(" bytes. As binary: ");
      for (int i = 0; i < bytes_received; i++){
        Serial.print(bytes_pulled[i], BIN);
        Serial.print(",");
      }
      Serial.println();
    }

    //wait briefly before reading the turns counter
    delayMicroseconds(100);

    setStateRS485(RS485_T_TX); //put the transciver into transmit mode
    delayMicroseconds(10);   //IO operations take time, let's throw in an arbitrary 10 microsecond delay to make sure the transeiver is ready

    //send the command to get position. All we have to do is send the node address, but we can use the modifier for consistency
    Serial1.write(addresses[encoder] | RS485_TURNS);

    //wait for command to finish transmitting
    while (!(UCSR1A & _BV(TXC1)));

    setStateRS485(RS485_T_RX); //set the transceiver back into receive mode for the encoder response

    //We need to give the encoder enough time to respond, but not too long. In a tightly controlled application we would want to use a timeout counter
    //to make sure we don't have any issues, but for this demonstration we will just have an arbitrary delay before checking to see if we have data to read.
    delayMicroseconds(30);

    //Response from encoder should be exactly 2 bytes
    bytes_received = Serial1.available();
    byte bytes_pulled_2[bytes_received];
    for(int i = 0; i<=bytes_received; i++){
      bytes_pulled_2[i] = Serial1.read();
    }
    if (bytes_received == 2)
    {
      uint16_t currentTurns = Serial1.read(); //low byte comes first
      currentTurns |= Serial1.read() << 8;    //high byte next, OR it into our 16 bit holder but get the high bit into the proper placeholder

      if (verifyChecksumRS485(currentTurns))
      {
        //we got back a good position, so just mask away the checkbits
        currentTurns &= 0x3FFF;

        Serial.print("Encoder #");
        Serial.print(encoder, DEC);
        Serial.print(" turns: ");
        Serial.println(currentTurns, DEC); //print the position in decimal format
      }
      else
      {
        Serial.print("Encoder #");
        Serial.print(encoder, DEC);
        Serial.print(" turns error: Invalid checksum. Given is: ");
        Serial.println(currentTurns, BIN);
      }
    }
    else
    {
      Serial.print("Encoder #");
      Serial.print(encoder, DEC);
      Serial.print(" error reading turns: Expected to receive 2 bytes. Actually received ");
      Serial.print(bytes_received, DEC);
      Serial.print(" bytes. In Binary: ");
      for (int i = 0; i < bytes_received; i++){
        Serial.print(bytes_pulled_2[i], BIN);
        Serial.print(",");
      }
      Serial.println();
    }
  }

  //flush the received serial buffer just in case anything extra got in there
  while (Serial1.available()) Serial1.read();

  //For the purpose of this demo we don't need the position returned that quickly so let's wait a half second between reads
  //delay() is in milliseconds
  delay(1000);
}

bool verifyChecksumRS485(uint16_t message)
{
  //using the equation on the datasheet we can calculate the checksums and then make sure they match what the encoder sent
  //checksum is invert of XOR of bits, so start with 0b11, so things end up inverted
  uint16_t checksum = 0x3;
  for(int i = 0; i < 14; i += 2)
  {
    checksum ^= (message >> i) & 0x3;
  }
  return checksum == (message >> 14);
}
/*
 * This function sets the state of the RS485 transceiver. We send it that state we want. Recall above I mentioned how we need to do this as quickly
 * as possible. To be fast, we are not using the digitalWrite functions but instead will access the avr io directly. I have shown the direct access
 * method and left commented the digitalWrite method.
 */
void setStateRS485(uint8_t state)
{
  //switch case to find the mode we want
  switch (state)
  {
    case RS485_T_TX:
      PORTH |= 0b01100000;
      //digitalWrite(RS485_RE, HIGH); //ph5
      //digitalWrite(RS485_DE, HIGH); //ph6
      break;
    case RS485_T_RX:
      PORTH &= 0b10011111;
      //digitalWrite(RS485_RE, LOW);
      //digitalWrite(RS485_DE, LOW);
      break;
    default:
      PORTH &= 0b10111111;
      PORTH |= 0b00100000;
      //digitalWrite(RS485_RE, HIGH);
      //digitalWrite(RS485_DE, LOW);
      break;
  }
}

Inside the error, what does this mean?

That's part of the original code. The encoder can track angular position and then when it goes over 360 degrees it counts the number of turns it has made. So the error refers to an error when the code is trying to calculate the number of turns that have been made.

So, "turns" are complete revolutions, plus any overshoot?

Does the encoder exhibit "bouncing" which is to say, does the counting mechanism report two turns together? Can you print the time the turns occur?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.