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