ModbusRTUSlave library and 32U4 not talking (solved: RS485 driver wiring issue)

Hi all,

I'm racking my brain trying to get a 32U4 (Itsy Bitsy 32u4 5V from Adafruit) to talk to Modbus (RS-485). I have an Uno here that I successfully got to talk, copied and pasted the code over to the 32u4 and made the required GPIO changes and serial port change (used Serial1 on pins 0 and 1), and basically the same code won't work on the 32u4. Specifically, as a test, I'm writing coil values to the slave device via the master, which is running on an Uno.

Here's the code that works on the Uno slave:

#if __AVR__
  // Uncomment the following line if you want to use SoftwareSerial on pins 10 and ll; note this only works on AVR boards.
# define USE_SOFTWARE_SERIAL
#endif

#if defined USE_SOFTWARE_SERIAL
  #include <SoftwareSerial.h>
#endif

#include <ModbusRTUSlave.h>

const byte potPins[2] = {A0, A1};
#if defined ESP32 || (defined ARDUINO_NANO_RP2040_CONNECT && !defined ARDUINO_ARCH_MBED)
const byte buttonPins[2] = {D3, D4};
const byte ledPins[4] = {D5, D6, D7, D8};
const byte dePin = D2;
#else
const byte buttonPins[2] = {3, 4};
const byte ledPins[4] = {5, 6, 7, 8};
const byte dePin = 2;
#endif

#if defined USE_SOFTWARE_SERIAL
  const byte rxPin = 10;
  const byte txPin = 11;
  SoftwareSerial mySerial(rxPin, txPin);
  ModbusRTUSlave modbus(mySerial, dePin);  // serial port, driver enable pin for rs-485
#else
  #if (defined __AVR_ATmega328P__ || defined __AVR_ATmega168__ || defined __AVR_ATmega1280__ || defined __AVR_ATmega2560__)
    ModbusRTUSlave modbus(Serial, dePin);  // serial port, driver enable pin for rs-485
  #elif defined ESP32
    ModbusRTUSlave modbus(Serial0, dePin); // serial port, driver enable pin for rs-485
  #else
    ModbusRTUSlave modbus(Serial1, dePin); // serial port, driver enable pin for rs-485
  #endif
#endif

bool coils[2];
bool discreteInputs[2];
uint16_t holdingRegisters[2];
uint16_t inputRegisters[2];

unsigned long lastSecond = 0;

void setup() {
  #if defined ESP32
    analogReadResolution(10);
  #endif

  pinMode(potPins[0], INPUT);
  pinMode(potPins[1], INPUT);
  pinMode(buttonPins[0], INPUT_PULLUP);
  pinMode(buttonPins[1], INPUT_PULLUP);
  pinMode(ledPins[0], OUTPUT);
  pinMode(ledPins[1], OUTPUT);
  pinMode(ledPins[2], OUTPUT);
  pinMode(ledPins[3], OUTPUT);

  modbus.configureCoils(coils, 2);                       // bool array of coil values, number of coils
  modbus.configureDiscreteInputs(discreteInputs, 2);     // bool array of discrete input values, number of discrete inputs
  modbus.configureHoldingRegisters(holdingRegisters, 2); // unsigned 16 bit integer array of holding register values, number of holding registers
  modbus.configureInputRegisters(inputRegisters, 2);     // unsigned 16 bit integer array of input register values, number of input registers

  Serial.begin(115200);
  modbus.begin(1, 38400);
}

void loop() {
  unsigned long currentMillis = millis();

  inputRegisters[0] = map(analogRead(potPins[0]), 0, 1023, 0, 255);
  inputRegisters[1] = map(analogRead(potPins[1]), 0, 1023, 0, 255);
  discreteInputs[0] = !digitalRead(buttonPins[0]);
  discreteInputs[1] = !digitalRead(buttonPins[1]);
  
  modbus.poll();

  analogWrite(ledPins[0], holdingRegisters[0]);
  analogWrite(ledPins[1], holdingRegisters[1]);
  digitalWrite(ledPins[2], coils[0]);
  digitalWrite(ledPins[3], coils[1]);

  if (currentMillis - lastSecond >= 1000) {
    lastSecond = currentMillis;

      Serial.print("IR0: ");
      Serial.print(inputRegisters[0]);
      Serial.print(" IR1: ");
      Serial.println(inputRegisters[1]);
      Serial.print("DI0: ");
      Serial.print(discreteInputs[0]);
      Serial.print(" DI1: ");
      Serial.println(discreteInputs[1]);
      Serial.print("C0: ");
      Serial.print(coils[0]);
      Serial.print(" C1: ");
      Serial.println(coils[1]);
      Serial.print("HR0: ");
      Serial.print(holdingRegisters[0]);
      Serial.print(" HR1: ");
      Serial.println(holdingRegisters[1]);
      Serial.println(digitalRead(buttonPins[0]));

    }
}

Here's the code that doesn't successfully communicate on my ItsyBitsy 32U4:

#include <ModbusRTUSlave.h>

const byte potPins[2] = {A0, A1};
const byte buttonPins[2] = {7, 8};
const byte ledPin = 13;
const byte dePin = 9;

ModbusRTUSlave modbus(Serial1); // serial port, driver enable pin for rs-485

bool coils[2];
bool discreteInputs[2];
uint16_t holdingRegisters[2];
uint16_t inputRegisters[2];

unsigned long lastSecond = 0;

void setup() {
  #if defined ESP32
    analogReadResolution(10);
  #endif

  pinMode(dePin, OUTPUT);
  digitalWrite(dePin, 0);

  pinMode(potPins[0], INPUT);
  pinMode(potPins[1], INPUT);
  pinMode(buttonPins[0], INPUT_PULLUP);
  pinMode(buttonPins[1], INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);

  modbus.configureCoils(coils, 2);                       // bool array of coil values, number of coils
  modbus.configureDiscreteInputs(discreteInputs, 2);     // bool array of discrete input values, number of discrete inputs
  modbus.configureHoldingRegisters(holdingRegisters, 2); // unsigned 16 bit integer array of holding register values, number of holding registers
  modbus.configureInputRegisters(inputRegisters, 2);     // unsigned 16 bit integer array of input register values, number of input registers

  Serial.begin(115200);
  modbus.begin(2, 38400);
}

void loop() {
  unsigned long currentMillis = millis();

  inputRegisters[0] = map(analogRead(potPins[0]), 0, 1023, 0, 255);
  inputRegisters[1] = map(analogRead(potPins[1]), 0, 1023, 0, 255);
  discreteInputs[0] = !digitalRead(buttonPins[0]);
  discreteInputs[1] = !digitalRead(buttonPins[1]);
  
  modbus.poll();

  analogWrite(ledPin, holdingRegisters[0]);
  digitalWrite(ledPin, coils[0]);

  if (currentMillis - lastSecond >= 1000) {
    lastSecond = currentMillis;

      Serial.print("IR0: ");
      Serial.print(inputRegisters[0]);
      Serial.print(" IR1: ");
      Serial.println(inputRegisters[1]);
      Serial.print("DI0: ");
      Serial.print(discreteInputs[0]);
      Serial.print(" DI1: ");
      Serial.println(discreteInputs[1]);
      Serial.print("C0: ");
      Serial.print(coils[0]);
      Serial.print(" C1: ");
      Serial.println(coils[1]);
      Serial.print("HR0: ");
      Serial.print(holdingRegisters[0]);
      Serial.print(" HR1: ");
      Serial.println(holdingRegisters[1]);
      Serial.println(digitalRead(buttonPins[0]));

    }
}

and here's what I'm running on my master:

#define SLAVE_ADDR 1
#include <ModbusRTUMaster.h>

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
  // The ATmega328P and ATmega168 only have one HardwareSerial port, and on Arduino boards it is usually connected to a USB/UART bridge.
  // So, for these boards, we will use SoftwareSerial with the lbrary, leaving the HardwareSerial port available to send debugging messages.
  #define SOFTWARE_SERIAL
  #include <SoftwareSerial.h>
  const int8_t rxPin = 10;
  const int8_t txPin = 11;
  SoftwareSerial mySerial(rxPin, txPin);
  #define MODBUS_SERIAL mySerial
#elif defined(ARDUINO_NANO_ESP32)
  // On the Arduino Nano ESP32, the HardwareSerial port on pins 0 and 1 is Serial0
  #define MODBUS_SERIAL Serial0
#else
  // On the majority of Arduino boards, the HardwareSerial port on pins 0 and 1 is Serial1
  // On the Arduino Mega and Adruino Due, Serial1 is on pins 18 and 19.
  #define MODBUS_SERIAL Serial1
#endif

#if (defined(ARDUINO_NANO_RP2040_CONNECT) && !defined(ARDUINO_ARCH_MBED)) || defined(ARDUINO_NANO_ESP32)
  // These boards operate unsing GPIO numbers that don't correspond to the numbers on the boards.
  // However they do have D# values #defined to correct this.
  const int8_t buttonPins[2] = {D2, D3};
  const int8_t ledPins[4] = {D5, D6, D7, D8};
  const int8_t dePin = D2;
#else
  // Other boards do not have D# values, and will throw an error if you try to use them.
  const int8_t buttonPins[2] = {12, 13};
  const int8_t ledPins[4] = {5, 6, 7, 8};
  const int8_t dePin = 2;
#endif
#define DEBUGGING_ENABLED

const int8_t knobPins[2] = {A0, A1};

ModbusRTUMaster modbus(MODBUS_SERIAL, dePin);

const uint8_t numCoils = 2;
const uint8_t numDiscreteInputs = 2;
const uint8_t numHoldingRegisters = 2;
const uint8_t numInputRegisters = 2;

bool slaveCoils[2] = {1, 1};
bool slaveDIs[2];
bool coils[numCoils];
bool discreteInputs[numDiscreteInputs];
uint16_t holdingRegisters[numHoldingRegisters];
uint16_t inputRegisters[numInputRegisters];

unsigned long lastSecond = 0;

unsigned long transactionCounter = 0;
unsigned long errorCounter = 0;

const char* errorStrings[] = {
  "success",
  "invalid id",
  "invalid buffer",
  "invalid quantity",
  "response timeout",
  "frame error",
  "crc error",
  "unknown comm error",
  "unexpected id",
  "exception response",
  "unexpected function code",
  "unexpected response length",
  "unexpected byte count",
  "unexpected address",
  "unexpected value",
  "unexpected quantity"
};



void printLog(uint8_t unitId, uint8_t functionCode, uint16_t startingAddress, uint16_t quantity, uint8_t error) {
  transactionCounter++;
  if (error) errorCounter++;
  char string[128];
  sprintf(string, "%ld %ld %02X %02X %04X %04X %s", transactionCounter, errorCounter, unitId, functionCode, startingAddress, quantity, errorStrings[error]);
  Serial.print(string);
  if (error == MODBUS_RTU_MASTER_BUF_SIZE) {
    sprintf(string, ": %02X", modbus.getExceptionResponse());
    Serial.print(string);
  }
  Serial.println();
}



void setup() {
  pinMode(knobPins[0], INPUT);
  pinMode(knobPins[1], INPUT);
  pinMode(buttonPins[0], INPUT_PULLUP);
  pinMode(buttonPins[1], INPUT_PULLUP);
  pinMode(ledPins[0], OUTPUT);
  pinMode(ledPins[1], OUTPUT);
  pinMode(ledPins[2], OUTPUT);
  pinMode(ledPins[3], OUTPUT);

  #if defined(ARDUINO_NANO_ESP32)
    analogReadResolution(10);
  #endif
  
  #if defined DEBUGGING_ENABLED
    Serial.begin(115200);
    while (!Serial);
  #endif

  // You can change the baud and config values if you like.
  // Just make sure they match the settings you use in ModbusRTUSlaveExample.
  // Note, the config value will be ignored when using SoftwareSerial.
  // SoftwareSerial only supports SERIAL_8N1.
  unsigned long baud = 38400;
  #ifndef SOFTWARE_SERIAL
    uint32_t config = SERIAL_8N1;
    MODBUS_SERIAL.begin(baud, config);
    modbus.begin(baud, config);
  #else
    MODBUS_SERIAL.begin(baud);
    modbus.begin(baud);
  #endif
}

void loop() {
  unsigned long currentMillis = millis();

  analogWrite(ledPins[0], inputRegisters[0]);
  analogWrite(ledPins[1], inputRegisters[1]);
  digitalWrite(ledPins[2], discreteInputs[0]);
  digitalWrite(ledPins[3], discreteInputs[1]);

    if (currentMillis - lastSecond >= 1000) {
    lastSecond = currentMillis;
      debug(modbus.writeMultipleCoils(SLAVE_ADDR, 0, slaveCoils, 2));
      debug(modbus.readDiscreteInputs(SLAVE_ADDR, 0, slaveDIs, 2));

      Serial.print("IR0: ");
      Serial.print(inputRegisters[0]);
      Serial.print(" IR1: ");
      Serial.println(inputRegisters[1]);
      Serial.print("DI0: ");
      Serial.print(inputRegisters[0]);
      Serial.print(" DI1: ");
      Serial.println(inputRegisters[1]);
      Serial.print("C0: ");
      Serial.print(coils[0]);
      Serial.print(" C1: ");
      Serial.println(coils[1]);
      Serial.print("HR0: ");
      Serial.print(holdingRegisters[0]);
      Serial.print(" HR1: ");
      Serial.println(holdingRegisters[1]);
      Serial.print("Slavecoil0: ");
      Serial.print(slaveCoils[0]);
      Serial.print(" Slavecoil1: ");
      Serial.println(slaveCoils[1]);
      Serial.print("SlaveDI0: ");
      Serial.print(slaveDIs[0]);
      Serial.print(" SlaveDI1: ");
      Serial.println(slaveDIs[1]);

    }
}

# if defined DEBUGGING_ENABLED
  bool debug(bool modbusRequest) {
    if (modbusRequest == true) {
      Serial.println("Success");
    }
    else {
      Serial.print("Failure");
      if (modbus.getTimeoutFlag() == true) {
        Serial.print(": Timeout");
        modbus.clearTimeoutFlag();
      }
      else if (modbus.getExceptionResponse() != 0) {
        Serial.print(": Exception Response ");
        Serial.print(modbus.getExceptionResponse());
        switch (modbus.getExceptionResponse()) {
          case 1:
            Serial.print(" (Illegal Function)");
            break;
          case 2:
            Serial.print(" (Illegal Data Address)");
            break;
          case 3:
            Serial.print(" (Illegal Data Value)");
            break;
          case 4:
            Serial.print(" (Server Device Failure)");
            break;
          default:
            Serial.print(" (Uncommon Exception Response)");
            break;
        }
        modbus.clearExceptionResponse();
      }
      Serial.println();
    }
    Serial.flush();
    return modbusRequest;
  }
#endif

I'm using a custom circuit for the RS-485 transceiver, which I screwed up in two ways, and have since fixed: I had R20 and R21 there to make it 3.3V compatible for a previous project (R20 is removed, R21 is shorted now), and I added a pullup to the DE pin. I do have DE and REn solder jumpered. I've also tried it with and without R13 (terminator) resistor switched into the circuit. R12 and R14 are not populated. All of these boards are on a common power ground. I did put a scope on pin 0, and there's data flowing in.

Any suggestions?

Thanks!

-Rodney

Well, I figured it out, but I think I'll leave this information here if it looks useful. Lessons learned:

  1. Tying DE and REn together and operating them as one pin works.
  2. DE/REn needs a 10k pullup.
  3. I had reversed my RS485 A and B wires during troubleshooting and didn't swap them back. I had fixed the DE pullup and when it didn't fix the problem, I wrote my original post. I then realized my A and B wires were crossed and BAM it worked.

Thanks!

-Rodney

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