ESP32 and EPEVER 2210AN

What I'm looking for is a SIMPLE example sketch to write and read (individual) data coming from or going to an EPEVER 2210AN with an ESP32. Browsing the internet, this and other fora, I found quite some examples, but all of 'em are much to complicated for what I need. Here is the list of all possible data coming from and/or going to the 2210AN.
So :

  • what libraries do I need ?
  • how can I (in an easy way !) retrieve a certain value. (e.g. x = read register hex 3100)
  • how can I (in an easy way !) write a certain value. (e.g write register hex 9001, y)

Maybe I'll have some more questions later, but I want to start at the simplest begin...

Anyone can guide me to a simple and basic example sketch ?

Thanks a lot in advance.

You need a library capable of doing Modbus RTU. There are several of them, each one has pros and drawbacks. As we have no details of your needs we cannot suggest one.

By using the library's methods.

By using the library's methods.

What hardware do you us (in addition to the ESP32) as the ESP32 doesn't have an on-chip RS485 and how did you wire it?

Thanks for your reply, pylon.
I need to read and write to the registers of a solar controller EPEVER 2210AN. This device can send out various data over an RS485 protocol. I used RS232 communication with my former solar controller and it worked fine for many years until I blew up that unit. The "old" unit is not available anymore so I have to adapt my sketch to be able to communicate with the new (RS485) unit.
So I am familiar with communicating over RS232, but not with the way to send or retrieve data from RS485 (MODBUS). I also know that I have to use an RS485-RS232 converter. (I'll use a board with MAX485 chip).
I'm looking for a SIMPLE library, that allows me to send and retrieve byte(s) over RS485(MODBUS)
HERE you can see all the registers from that 2210AN solar charger.
The examples I found here and on the internet are much to complicated for what I need. Maybe later I do some more "tricks" like wifi, bluetooth etc ... but first I want to start with the (very) basics...
I need to READ a register and assign it to variable (byte, int, long)
I need to WRITE a variable to a register.
no more, no less (for the moment :slight_smile: )

again : thanks for your effort !

E.G. :
float Ubat = readRegister?(3004);
writeRegister?(9001,100);

RS232 or RS485 are the physical interfaces, Modbus is the protocol your controller uses for communication. It commonly uses RS485 as the physical layer but you can also use it over RS232.

No, a RS485-CMOS converter, not one with a MAX485 chip as that one is TTL (5V). RS232 uses different voltages, none of the current Arduino board has an RS232 interface on-board.

The ModbusMaster library might be what you're looking for but there are alternatives if you don't like it.

Modbus registers are always 16bit unsigned integers. If they should transport a float value there is no standard for that and the manufacturer has to define in the manual how the float is split up to for example two registers.

I know that, pylon :slight_smile: The equation should be
float Ubat = (readRegister?(3004)) / 100.0; The charger sends a 16bit value that represents the (real value * 100). If e.g. the battery voltage is 12.34V, the controller sends 1234.

I see in different examples that the MAX485 board does work with 3.3V. I'll try it out myself once I found a working library. if it doesn't work, I'll use a bidirectional voltage converter...

I know this. My solar charger uses MODBUS protocol over RS485, the MAX485 board "translates" the RS485 to RS232. (Any Arduino can deal with RS232 (wire.h)) My old solar charger used it (pins 16 and 17 on ESP32). I could connect the charger straight to the ESP32 because this old charger sent out RS232 on 3.3V

So my main question remains : an easy way to read and write to MODBUS registers with Arduino (in this case ESP32) ...
The technical/electronics issues I can handle mostly likely myself because I am certified electronics engineer. So before I start working on the right voltage levels etc., I need a simple (example) MODBUS sketch that WORKS.. :wink:

Nevertheless : 1K THANKS for your interest and effort ! :sunglasses:

Not according to the datasheet (4.75V- 5.25V). I do never operate chips outside the datasheet specification if I'm not forced to.

Wrong. First, no current Arduino board can deal with RS232. Second, Wire.h is the library for the I2C interface in the Arduino IDE, not for UART communication (which both, RS232 and RS485 are based on).

Not the standard ones. They most often translate to TTL levels (5V) not RS232 levels (-3 to -15V for mark, 3 to 15V for space). You can destroy any Arduino if you put RS232 levels to it's pins.

As above: RS232 is not at 3.3V. You confuse the term RS232 with UART I guess.

I already answered that question: ModbusMaster (it's available in the Library Manager). There are different minds about "easy" but I think that interface is quite easy to use and probably matches most common use cases.

OK, thanks.
I'll study the ModbusMaster library, use a 3.3<>5V converter and (with trial and error) I'll find my way.
thanks again for your info.

This reads a EPEVER Tracer series.

It's based on DocWalkers ModbusMaster

/*******************************************************

  Modbus Client Example 0x60 (Modbus Master)
  Read EPSOLAR - all registers

  This Modbus Client
  - reads registers from Modbus Servers periodically

  based on an idea

  RS485_HalfDuplex.pde - example using ModbusMaster library to communicate
  with EPSolar LS2024B controller using a half-duplex RS485 transceiver.

  This example is tested against an EPSolar LS2024B solar charge controller.
  See here for protocol specs:
  http://www.solar-elektro.cz/data/dokumenty/1733_modbus_protocol.pdf

  hardware
  - a LED
  - 4 push buttons
  - a MAX485-TTL adapter

  by noiasca
  2022-07-30

 *******************************************************/


/* *******************************************************
   Serial Interface
 * **************************************************** */
// if you don't have enough HW Serial (i.e. on an UNO)
// you are forced to use SoftwareSerial or AltSoftSerial
//include <SoftwareSerial.h>
//constexpr uint8_t rxPin = 2;                   // for Softserial
//constexpr uint8_t txPin = 3;
//SoftwareSerial mySerial(rxPin, txPin);

// On a Mega you can simply use
// a Reference to an existing HW Serial:
HardwareSerial &mySerial = Serial3;

/* **************************************************** *
   Modbus
 * **************************************************** */

#include <ModbusMaster.h>                        // Modbus Master 2.0.0 by Doc Walker - install with Library Manager
constexpr uint8_t modbusEnablePin = 5;           // The GPIO used to control the MAX485 TX pin. Set to 255 if you are not using RS485 or a selfsensing adapter
constexpr uint32_t modbusBaud = 115200;          // use slow speeds with SoftSerial
constexpr uint16_t modbusRestTx = 15000;         // rest time between transmissions - microseconds
uint32_t modbusPreviousTx = 0;                   // timestamp of last transmission - microseconds
ModbusMaster serverA;                            // instantiate ModbusMaster object - slave - node

// this function will be called before the client transmits data
void preTransmission()
{
  while (micros() - modbusPreviousTx < modbusRestTx)   // check last transmission end and wait if the call was to early
  {
    yield();                                           // wait some time and allow background tasks
  }
  digitalWrite(modbusEnablePin, HIGH);
}

// this function will be called after the transmission
void postTransmission()
{
  digitalWrite(modbusEnablePin, LOW);
  modbusPreviousTx = micros();         // remember last timestamp
}

// do all the settings for the Modbus
void modbusInit()
{
  mySerial.begin(modbusBaud);                     // initialize Modbus communication baud rate
  serverA.begin(1, mySerial);                     // communicate with Modbus server ID over the given Serial interface
  pinMode(modbusEnablePin, OUTPUT);               // Init enable pins for modbus master library
  digitalWrite(modbusEnablePin, LOW);
  serverA.preTransmission(preTransmission);       // Callbacks allow us to configure the RS485 transceiver correctly
  serverA.postTransmission(postTransmission);
}

/* **************************************************** *
   Modbus - Implementation
 * **************************************************** */

void requestData()
{
  static uint32_t previousMillis = -5000;  // timestamp of last request
  static uint8_t actual = 0;           // actual iteration
  uint32_t currentMillis = millis();

  if (currentMillis - previousMillis > 5000)  // set the interval in ms
  {
    previousMillis = currentMillis;
    uint16_t reg = 0x3100;
    int result;
    switch (actual)
    {
      case 0 :
        reg = 0x3100;
        result = serverA.readInputRegisters(reg, 8);
        if (result == serverA.ku8MBSuccess) // do something if read is successfull
        {
          Serial.print(F("Charging equipment input V:     ")); Serial.println(serverA.getResponseBuffer(0x00) / 100.0f);
          Serial.print(F("Charging equipment input A:     ")); Serial.println(serverA.getResponseBuffer(0x01) / 100.0f);
          Serial.print(F("Charging equipment input W:     ")); Serial.println((serverA.getResponseBuffer(0x02) + ((uint32_t)serverA.getResponseBuffer(0x03) << 16)) / 100.0f); // cast and brackets
          Serial.print(F("Charging equipment output V:    ")); Serial.println(serverA.getResponseBuffer(0x04) / 100.0f);
          Serial.print(F("Charging equipment output A:    ")); Serial.println(serverA.getResponseBuffer(0x05) / 100.0f);
          Serial.print(F("Charging equipment output W:    ")); Serial.println((serverA.getResponseBuffer(0x06) + ((uint32_t)serverA.getResponseBuffer(0x07) << 16)) / 100.0f); // cast and brackets
        }
        else
        {
          Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
        }
        actual++;
        break;
      case 1 :
        reg = 0x310C;
        result = serverA.readInputRegisters(reg, 7);
        if (result == serverA.ku8MBSuccess) // do something if read is successfull
        {
          Serial.print(F("Charging equipment input V:     ")); Serial.println(serverA.getResponseBuffer(0x310c - reg) / 100.0f);
          Serial.print(F("Charging equipment input A:     ")); Serial.println(serverA.getResponseBuffer(0x310d - reg) / 100.0f);
          Serial.print(F("Charging equipment input W:     ")); Serial.println((serverA.getResponseBuffer(0x310e - reg) + ((uint32_t)serverA.getResponseBuffer(0x310F - reg) << 16)) / 100.0f); // cast and brackets
          Serial.print(F("Battery Temperature:            ")); Serial.println(serverA.getResponseBuffer(0x3110 - reg) / 100.0f);
          Serial.print(F("Temperature inside equipment:   ")); Serial.println(serverA.getResponseBuffer(0x3111 - reg) / 100.0f);
          Serial.print(F("Power components temperature:   ")); Serial.println(serverA.getResponseBuffer(0x3112 - reg) / 100.0f);
        }
        else
        {
          Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
        }
        actual++;
        break;
      case 2 :
        reg = 0x311A;
        result = serverA.readInputRegisters(reg, 2);
        if (result == serverA.ku8MBSuccess) // do something if read is successfull
        {
          Serial.print(F("Battery SoC:                    ")); Serial.println(serverA.getResponseBuffer(0x311A - reg));
          Serial.print(F("Remote battery temperature:     ")); Serial.println(serverA.getResponseBuffer(0x311B - reg) / 100.0f);
        }
        else
        {
          Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
        }
        actual++;
        break;
      case 3 :
        reg = 0x311A;
        result = serverA.readInputRegisters(reg, 1);
        if (result == serverA.ku8MBSuccess) // do something if read is successfull
        {
          Serial.print(F("Battery's real rated power:     ")); Serial.println(serverA.getResponseBuffer(0x311D - reg) / 100.0f);
        }
        else
        {
          Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
        }
        actual++;
        break;
      case 4 :
        reg = 0x3200;
        result = serverA.readInputRegisters(reg, 2);
        if (result == serverA.ku8MBSuccess) // do something if read is successfull
        {
          Serial.print(F("Battery status:                 ")); Serial.println(serverA.getResponseBuffer(0x3200 - reg), BIN);
          Serial.print(F("Charging equipment:             ")); Serial.println(serverA.getResponseBuffer(0x3201 - reg), BIN);
        }
        else
        {
          Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
        }
        actual++;
        break;
      case 5 :
        reg = 0x3300;
        result = serverA.readInputRegisters(reg, 30);
        if (result == serverA.ku8MBSuccess) // do something if read is successfull
        {
          Serial.print(F("Max input (PV) volt:            ")); Serial.println(serverA.getResponseBuffer(0x3300 - reg) / 100.0f);
          Serial.print(F("Min input (PV) volt:            ")); Serial.println(serverA.getResponseBuffer(0x3301 - reg) / 100.0f);
          Serial.print(F("Max battery volt:               ")); Serial.println(serverA.getResponseBuffer(0x3302 - reg) / 100.0f);
          Serial.print(F("Min battery volt:               ")); Serial.println(serverA.getResponseBuffer(0x3303 - reg) / 100.0f);
          Serial.print(F("Consumed energy today:          ")); Serial.println((serverA.getResponseBuffer(0x3304 - reg) + ((uint32_t)serverA.getResponseBuffer(0x3305 - reg) << 16)) / 100.0f);
          Serial.print(F("Consumed energy month:          ")); Serial.println((serverA.getResponseBuffer(0x3306 - reg) + ((uint32_t)serverA.getResponseBuffer(0x3307 - reg) << 16)) / 100.0f);
          Serial.print(F("Consumed energy year:           ")); Serial.println((serverA.getResponseBuffer(0x3308 - reg) + ((uint32_t)serverA.getResponseBuffer(0x3309 - reg) << 16)) / 100.0f);
          Serial.print(F("Total consumed:                 ")); Serial.println((serverA.getResponseBuffer(0x330A - reg) + ((uint32_t)serverA.getResponseBuffer(0x330B - reg) << 16)) / 100.0f);
          Serial.print(F("Generated energy today:         ")); Serial.println((serverA.getResponseBuffer(0x330C - reg) + ((uint32_t)serverA.getResponseBuffer(0x330D - reg) << 16)) / 100.0f);
          Serial.print(F("Generated energy month:         ")); Serial.println((serverA.getResponseBuffer(0x330E - reg) + ((uint32_t)serverA.getResponseBuffer(0x330F - reg) << 16)) / 100.0f);
          Serial.print(F("Generated energy year:          ")); Serial.println((serverA.getResponseBuffer(0x3310 - reg) + ((uint32_t)serverA.getResponseBuffer(0x3311 - reg) << 16)) / 100.0f);
          Serial.print(F("Total generated energy:         ")); Serial.println((serverA.getResponseBuffer(0x3312 - reg) + ((uint32_t)serverA.getResponseBuffer(0x3313 - reg) << 16)) / 100.0f);
          Serial.print(F("CO2 reduction:                  ")); Serial.println((serverA.getResponseBuffer(0x3314 - reg) + ((uint32_t)serverA.getResponseBuffer(0x3315 - reg) << 16)) / 100.0f);
          // there is one register not documented
          Serial.print(F("Battery Temperature :           ")); Serial.println(serverA.getResponseBuffer(0x331D - reg) / 100.0f);
          Serial.print(F("Ambient Temperature :           ")); Serial.println(serverA.getResponseBuffer(0x331E - reg) / 100.0f);
        }
        else
        {
          Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
        }
        actual = 0; // in the last case we jump back to 0 instead of actual++;
        break;
      default : actual = 0;
    } // switch
  }
}


// Parameters don't change so often, we only read them once after startup
// preTransmission and postTransmission take care of the requested "delay" between requests
void requestParameter()
{
  uint16_t reg;
  int result;

  reg = 0x3000;
  result = serverA.readInputRegisters(reg, 8);
  if (result == serverA.ku8MBSuccess) // do something if read is successfull
  {
    Serial.print(F("Charging equipment rated input V: ")); Serial.println(serverA.getResponseBuffer(0x00) / 100.0f);
    Serial.print(F("Charging equipment rated input A: ")); Serial.println(serverA.getResponseBuffer(0x01) / 100.0f);
    Serial.print(F("Charging equipment rated input W: ")); Serial.println((serverA.getResponseBuffer(0x02) + ((uint32_t)serverA.getResponseBuffer(0x03) << 16)) / 100.0f); // cast and brackets
    Serial.print(F("Charging equipment rated output V:")); Serial.println(serverA.getResponseBuffer(0x04) / 100.0f);
    Serial.print(F("Charging equipment rated output A:")); Serial.println(serverA.getResponseBuffer(0x05) / 100.0f);
    Serial.print(F("Charging equipment rated output W:")); Serial.println((serverA.getResponseBuffer(0x06) + ((uint32_t)serverA.getResponseBuffer(0x07) << 16)) / 100.0f); // cast and brackets
    Serial.print(F("Charging mode:                    ")); Serial.println(serverA.getResponseBuffer(0x08));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x300E;
  result = serverA.readInputRegisters(reg, 1);
  if (result == serverA.ku8MBSuccess) // do something if read is successfull
  {
    Serial.print(F("Rated output current of load:     ")); Serial.println(serverA.getResponseBuffer(0x00) / 100.0f);
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x9000;
  result = serverA.readHoldingRegisters(reg, 15);
  if (result == serverA.ku8MBSuccess) // do something if read is successfull
  {
    Serial.print(F("Battery Type:                   ")); Serial.println(serverA.getResponseBuffer(0x9000 - reg));
    Serial.print(F("Battery Capacity:               ")); Serial.println(serverA.getResponseBuffer(0x9001 - reg));
    Serial.print(F("Temperature compensation coeff: ")); Serial.println(serverA.getResponseBuffer(0x9002 - reg) / 100.0f);
    Serial.print(F("High Volt disconnect:           ")); Serial.println(serverA.getResponseBuffer(0x9003 - reg) / 100.0f);
    Serial.print(F("Charging limit voltage:         ")); Serial.println(serverA.getResponseBuffer(0x9004 - reg) / 100.0f);
    Serial.print(F("Over voltage reconnect:         ")); Serial.println(serverA.getResponseBuffer(0x9005 - reg) / 100.0f);
    Serial.print(F("Equalization voltage:           ")); Serial.println(serverA.getResponseBuffer(0x9006 - reg) / 100.0f);
    Serial.print(F("Boost voltage:                  ")); Serial.println(serverA.getResponseBuffer(0x9007 - reg) / 100.0f);
    Serial.print(F("Float voltage:                  ")); Serial.println(serverA.getResponseBuffer(0x9008 - reg) / 100.0f);
    Serial.print(F("Boost reconnect voltage:        ")); Serial.println(serverA.getResponseBuffer(0x9009 - reg) / 100.0f);
    Serial.print(F("Low voltage reconnect:          ")); Serial.println(serverA.getResponseBuffer(0x900A - reg) / 100.0f);
    Serial.print(F("Under voltage recover:          ")); Serial.println(serverA.getResponseBuffer(0x900B - reg) / 100.0f);
    Serial.print(F("Under voltage warning:          ")); Serial.println(serverA.getResponseBuffer(0x900C - reg) / 100.0f);
    Serial.print(F("Low voltage disconnect:         ")); Serial.println(serverA.getResponseBuffer(0x900D - reg) / 100.0f);
    Serial.print(F("Discharging limit voltage recon:")); Serial.println(serverA.getResponseBuffer(0x900E - reg) / 100.0f);
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x9013;
  result = serverA.readHoldingRegisters(reg, 15);
  if (result == serverA.ku8MBSuccess) // do something if read is successfull
  {
    Serial.print(F("RTC:                            ")); Serial.println(serverA.getResponseBuffer(0x9013 - reg));
    Serial.print(F("RTC:                            ")); Serial.println(serverA.getResponseBuffer(0x9014 - reg));
    Serial.print(F("RTC:                            ")); Serial.println(serverA.getResponseBuffer(0x9015 - reg));
    Serial.print(F("Equalization charging cycle:    ")); Serial.println(serverA.getResponseBuffer(0x9016 - reg));
    Serial.print(F("Batterie Warning upper Limit:   ")); Serial.println(serverA.getResponseBuffer(0x9017 - reg));
    Serial.print(F("Batterie Warning upper Limit:   ")); Serial.println(serverA.getResponseBuffer(0x9018 - reg));
    Serial.print(F("Controller inner temperature upper limit: ")); Serial.println(serverA.getResponseBuffer(0x9019 - reg)/ 100.0f);
    Serial.print(F("Controller inner temperature upper limit recover: ")); Serial.println(serverA.getResponseBuffer(0x901A - reg)/ 100.0f);
    Serial.print(F("Power component temperature upper limit: ")); Serial.println(serverA.getResponseBuffer(0x901B - reg)/ 100.0f);    
    Serial.print(F("Power component temperature upper limit recover: ")); Serial.println(serverA.getResponseBuffer(0x901C - reg)/ 100.0f);    
    Serial.print(F("Line impedance:                 ")); Serial.println(serverA.getResponseBuffer(0x901D - reg)/ 100.0f);    
    Serial.print(F("Night Time Threshold Volt:      ")); Serial.println(serverA.getResponseBuffer(0x901C - reg)/ 100.0f);    
    Serial.print(F("Light signal startup night delay:")); Serial.println(serverA.getResponseBuffer(0x901F - reg));    
    Serial.print(F("Day Time Threshold Volt:        ")); Serial.println(serverA.getResponseBuffer(0x9020 - reg)/ 100.0f);    
    Serial.print(F("Light signal startup day delay: ")); Serial.println(serverA.getResponseBuffer(0x9021 - reg));    
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x903D;
  result = serverA.readHoldingRegisters(reg, 3);
  if (result == serverA.ku8MBSuccess) // do something if read is successfull
  {
    Serial.print(F("Load controlling modes:         ")); Serial.println(serverA.getResponseBuffer(0x903D - reg));
    Serial.print(F("Working time length 1:          ")); Serial.println(serverA.getResponseBuffer(0x903E - reg));
    Serial.print(F("Working time length 2:          ")); Serial.println(serverA.getResponseBuffer(0x903F - reg));  
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x9042;
  result = serverA.readHoldingRegisters(reg, 12);
  if (result == serverA.ku8MBSuccess) // do something if read is successfull
  {
    Serial.print(F("Turn on timing 1 (sec):         ")); Serial.println(serverA.getResponseBuffer(0x9042 - reg));
    Serial.print(F("Turn on timing 1 (min):         ")); Serial.println(serverA.getResponseBuffer(0x9043 - reg));
    Serial.print(F("Turn on timing 1 (hou):         ")); Serial.println(serverA.getResponseBuffer(0x9044 - reg)); 
    Serial.print(F("Turn off timing 1 (sec):        ")); Serial.println(serverA.getResponseBuffer(0x9045 - reg));
    Serial.print(F("Turn off timing 1 (min):        ")); Serial.println(serverA.getResponseBuffer(0x9046 - reg));
    Serial.print(F("Turn off timing 1 (hou):        ")); Serial.println(serverA.getResponseBuffer(0x9047 - reg)); 
    Serial.print(F("Turn on timing 2 (sec):         ")); Serial.println(serverA.getResponseBuffer(0x9048 - reg));
    Serial.print(F("Turn on timing 2 (min):         ")); Serial.println(serverA.getResponseBuffer(0x9049 - reg));
    Serial.print(F("Turn on timing 2 (hou):         ")); Serial.println(serverA.getResponseBuffer(0x904A - reg)); 
    Serial.print(F("Turn off timing 2 (sec):        ")); Serial.println(serverA.getResponseBuffer(0x904B - reg));
    Serial.print(F("Turn off timing 2 (min):        ")); Serial.println(serverA.getResponseBuffer(0x904C - reg));
    Serial.print(F("Turn off timing 2 (hou):        ")); Serial.println(serverA.getResponseBuffer(0x904D - reg));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x9065;
  result = serverA.readHoldingRegisters(reg, 1);
  if (result == serverA.ku8MBSuccess)
  {
    Serial.print(F("Length of Night:                ")); Serial.println(serverA.getResponseBuffer(0x9065 - reg));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x9067;
  result = serverA.readHoldingRegisters(reg, 1);
  if (result == serverA.ku8MBSuccess)
  {
    Serial.print(F("Battery rated voltage code:     ")); Serial.println(serverA.getResponseBuffer(0x9067 - reg));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x9069;
  result = serverA.readHoldingRegisters(reg, 7);
  if (result == serverA.ku8MBSuccess)
  {
    Serial.print(F("Load timing control selection:  ")); Serial.println(serverA.getResponseBuffer(0x9069 - reg));
    Serial.print(F("Default load on/off:            ")); Serial.println(serverA.getResponseBuffer(0x906A - reg));
    Serial.print(F("Equalize Duration:              ")); Serial.println(serverA.getResponseBuffer(0x906B - reg));
    Serial.print(F("Boost Duration:                 ")); Serial.println(serverA.getResponseBuffer(0x906C - reg));
    Serial.print(F("Discharge percentage:           ")); Serial.println(serverA.getResponseBuffer(0x906D - reg)/100.0f);
    Serial.print(F("Charging percentage:            ")); Serial.println(serverA.getResponseBuffer(0x906E - reg)/100.0f);
    Serial.print(F("Management Mode of battery:     ")); Serial.println(serverA.getResponseBuffer(0x9070 - reg));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }
  
  reg = 0x2;
  result = serverA.readCoils(reg, 1);
  if (result == serverA.ku8MBSuccess)
  {
    Serial.print(F("Manual control the load:        ")); Serial.println(serverA.getResponseBuffer(0x2 - reg));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x5;
  result = serverA.readCoils(reg, 2);
  if (result == serverA.ku8MBSuccess)
  {
    Serial.print(F("Enable load test mode:          ")); Serial.println(serverA.getResponseBuffer(0x5 - reg));
    Serial.print(F("Force the load on/off:          ")); Serial.println(serverA.getResponseBuffer(0x6 - reg));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x2000;
  result = serverA.readDiscreteInputs(reg, 1);
  if (result == serverA.ku8MBSuccess)
  {
    Serial.print(F("Over temperature inside the device: ")); Serial.println(serverA.getResponseBuffer(0x2000 - reg));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }

  reg = 0x200C;
  result = serverA.readDiscreteInputs(reg, 1);
  if (result == serverA.ku8MBSuccess)
  {
    Serial.print(F("Day/Night:                      ")); Serial.println(serverA.getResponseBuffer(0x200C - reg));
  }
  else
  {
    Serial.print(F(" ServerA no success register ")); Serial.print(reg, HEX); Serial.print(F(" result=")); Serial.println(result, HEX);
  }
}

/* **************************************************** *
   setup and loop
 * **************************************************** */

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Modbus Client Example 0x60"));

  modbusInit();
  requestParameter();
}

void loop()
{
  requestData();
}

if you need a ModbusServer (Slave) sketch to simulate such a solar charger, you can find an example in my Modbus Server Library: Arduino: Noiasca Modbus Server Library (Modbus Slave)

1 Like

Thank you so much noiasca !!!
This is much more than I asked for ! Nevertheless it seems to be the COMPLETE answer to my question(s) !
I don't know if I'll have the time to check it out in the next few days, but by the end of next week I'll certainly come back here to tell you the results of my tests. But just by READING your sketch, it looks VERY promising !
So : already a 100K thanks !!! :wink:

Hey noiasca ! :slightly_smiling_face:
I tried your very promising solution, but it didn't work :roll_eyes:. Probably I made a mistake somewhere.
The Serial monitor shows :

Modbus Client Example 0x60
ServerA no success register 3000 result=E0
ServerA no success register 300E result=E0
ServerA no success register 9000 result=E0
ServerA no success register 9013 result=E0
etc...

What have I done so far :

  • checked all connections -> OK. (using real PCB, not breadboard !)
  • checked UTP cable -> OK.
  • checked with oscilloscope TX through level shifter -> OK (used "blink.ino")
  • checked RX -> I see some activity : 5V with pulses to 3V on high side of level converter, 3.3V to 2.5V on low side.
  • swapped converter and level shifter with new ones.
  • put 10K resistor from A to +5V, and B to GND -> no results, so took 'em out again.

You can find the diagram that I used [here(Jottacloud)]

So if you have any suggestions/remarks/experiences : please let me know
If you have questions : don't hesitate to ask !

Nevertheless : Thanks in advance for your interest and efforts ! :wink:

that's interesting.

E0 means

    /**
    ModbusMaster invalid response slave ID exception.
    
    The slave ID in the response does not match that of the request.
    
    @ingroup constant
    */
    static const uint8_t ku8MBInvalidSlaveID             = 0xE0;

from: ModbusMaster/ModbusMaster.h at master · syvic/ModbusMaster · GitHub

if the communication is valid that could mean, that you send to a Slave X, but you receive the answer from Slave Y. I don't think that this is true for bought device (your EPEVER).

could it be that you mixed up TX/RX lines and or DI/DO or A/B??

Check the lines with your scope. Do you see "pulses"? Does the RX pulses look like "several bytes communication" or are there just some peaks?

may be try the MAX485 module on 3V3 without level shifting.

my next best guess would be to generate valid packet manually, send it to the slave and see what the slave is responding

WOW !!! thanks for your fast reply, noiasca !
I guess you replied just a few µseconds after I released the "SEND" button ! :smile: THANKS !

could it be that you mixed up TX/RX lines and or DI/DO or A/B??
I confess at first I made a mistake : I mixed up the outputs of the level shifter to the converter, but the result was the same... and I fixed that before crying out for help :smiley:
So now I have :

ESP32-------------------3.3 ->5V----------MAX485
GPIO 16 (RX2) ---- RXO(ch1) -RX1--------RO
GPIO 17 (TX2)------TXI (ch1) -TXO ------- DI
GPIO 27 -------------TXI (ch2)- TXO ------- DE+RE
A(epever 5-6) --------------------------------A checked from the other end of the 3-meterUTP cable
B(epever 3-4) --------------------------------B checked from the other end of the 3-meterUTP cable
And all GND's connected of course.
All checked with ohm meter.
I guess it's right ? ...

Check the lines with your scope. Do you see "pulses"? Does the RX pulses look like "several bytes communication" or are there just some peaks?

I just see one puls on GPIO16(RX2) going from 2.5V to 1.5V ...(about 0.2 mS) every 1000ms (for the test I used 1000 instead of 5000ms interval)

maybe try the MAX485 module on 3V3 without level shifting.

I'v read (almost) everywhere that MAX485 does not work reliably with 3.3V (That's why they made a MAX3485, but I don't have these handy)

my next best guess would be to generate valid packet manually, send it to the slave and see what the slave is responding

Yes ! that would be an interesting thing to do, but how do I do that ? I don't have any other Modbus device handy, let alone an analyzer...

So ... sorry to bother you again... :face_holding_back_tears:

Here you can see my pcb
Nothing else but the "modbus-system" has been mounted yet. I first need this modbus working before mount the display and other stuff ...

** your pcb **

I can't check your PCB reliable, but it seems there a some unconnected lines.

** your connection **

A is not on 2 pins. Check your manual ... as far as I remember, I have only connected ONE line for A and ONE line for B.

tests on physical layer:

zoom in ... is it really just one pulse?
If confirmed, go back a step check the TX line.
Do you see the client SENDING bytes (the request?
if you see them on TX,
check The A / B lines.
Do you see the bytes on A compared to TX? The signal level will diver, between TX and A, but the bytes should look very similar on the scope.
If yes - check the A line ... if there is NOTHING after TX has ended, the slave is not sending data.
If you see "bytes" on A after TX ... it seems that the slave is responding something. And that should be more than just a single spike.
If you have seen the answer of the Slave on A, check why you loose them on RX.

--> Follow the signal from the client in each single step to the server and backwards.

I hope this makes still sense for you.

on software level:
You could try to expand a pass through sketch.
That sketch simply echos from one serial to another.
additionally I've added sending of a simple message 3 seconds after start.

It requests 2 bytes from 0x311A ... that should be SoC and bat temperature

if you just open that in Serial-Monitor - you should see the program start.
If you just using the same Serial for modbus and debugging you will see same cryptic characters
in Serial - that's the hex you sent out.

Modbus Diagnostic - Send Once - than Passthrough
x{]€

If the Slave is responding - you should see the bytes from the Slave (in HEX).

untested test sketch:

/*
   Modbus Diagnostic - Send test - Passthrough
   - send a message once
   - prints each received byte on SoftSerial to Serial.

   by noiasca https://werner.rothschopf.net/

   2022-11-17 first tests
*/

constexpr byte modbus_enable_pin = 11;           // The GPIO used to control the MAX485 TX pin. Set to 255 if you are not using RS485 or a selfsensing adapter
constexpr byte rxPin = 2;                        // for Softserial
constexpr byte txPin = 3;

/* *******************************************************
   Serial Interface
 * **************************************************** */
// if you don't have enough HW Serial (i.e. on an UNO)
// you are forced to use SoftwareSerial or AltSoftSerial
//#include <SoftwareSerial.h>
//SoftwareSerial mySerial(rxPin, txPin);               // RXpin, TXpin

// On a Mega you can simply use
// a Reference to an existing HW Serial:
// HardwareSerial &mySerial = Serial3;

// If you don't need debugging output, you can even use
// a reference to Serial
HardwareSerial &mySerial = Serial;

constexpr byte modbus_format = SERIAL_8N1;       // if you can use Hardware Serial, prefered value is SERIAL_8N2
constexpr uint32_t modbus_baud = 19200;          // use slow speeds with SoftSerial


// send a test message
void sendtest()
{
  byte transmit[] {0x01,            // server
                   0x04,            // function code 04 read input register
                   0x31, 0x1A,      // data address
                   0x00, 0x02,      // total number of registers
                   0x5E, 0xF0       // CRC
                  };
  digitalWrite(modbus_enable_pin, HIGH);
  delay(1);
  for (size_t i = 0; i < sizeof(transmit); i++)
  {
    mySerial.write(transmit[i]);
  }
  mySerial.flush();
  digitalWrite(modbus_enable_pin, LOW);
}

void setup() {
  Serial.begin(19200);
  Serial.println(F("\nModbus Diagnostic - Send Once - than Passthrough"));
  mySerial.begin(modbus_baud);
  pinMode(modbus_enable_pin, OUTPUT);
  digitalWrite(modbus_enable_pin, LOW);
  delay(2000); //wait a little bit before the first test
  sendtest();
}

void loop() {
  static uint8_t counter = 0;
  const uint8_t linefeed = 20;
  static uint32_t previousMillis = 0;
  const uint16_t timeout = 1000; // automatic linefeed after ms idle

  if (mySerial.available())
  {
    Serial.print(" ");
    Serial.print(mySerial.read(), HEX);
    counter++;
    previousMillis = millis();
    if (counter >= linefeed)
    {
      counter = 0;
      Serial.println();
    }
  }
  if (millis() - previousMillis > timeout && counter > 0)
  {
    Serial.println();
    counter = 0;
  }
}

**
P.S.: you should really get some test gear ... a RS485-USB converter to see with your PC what's happening on your modbus, some testsoftware for the modbus.

Hey noiasca,

Thanks again 0x3E8 times for your incredible fast reply. !
Probably I don't have a lot of time today to do/check the things you proposed. Again they seem very promising !
The question about the double A and B lines I found in the (Modbus) manual of the Epever.
You can find a copy HERE
Check the page 1 for the protocol and page 13 for the A and B connections.

I gotto run now, but ... "I'll be back" :smiley: ASAP !

Again : thanks again ! (and again...)

I see. I only have the older series (10A and 40A - but without AN in the model name). And I'm using only one A and one B line. But your scope will tell you more.

Hey noiasca :slight_smile: ,

Had a few moments to do some checks. I checked the relation between DE/RE and TX (measured at 5V side of the 3.3/5V converter... (not so easy with an analog oscilloscope to measure events that happen every 5 seconds ... :roll_eyes: )


So far so good, I guess ???
maybe more tomorrow ... :roll_eyes:

Anyhow : thanks for your interest !

this shows only TX.
(DE/RE is not so from interest, as it is just a HIGH during transmission, but ok. We see that digitalpin is still working... only thing ... does it show only a level of roughly 3V? ... do you have a levelshifter on that pin also?).
Now scope the A Line (in stead of DE/RE).

Thanks again !
I use the RE/DE line mainly for triggering (when RE/DE is LOW : nothing (should) happen).
Because RE/DE comes from GPIO27 (3.3V), I do use a levelconverter to 5V.
As you see on the picture below (sorry for the bad quality) the A-line is equal to the TX line and I see the same signal at the other end of the UTP cable (pin 5 and 6)
So far so good I would say, no ?
One remark : I see that RE/DE nor A go completely to zero volts, but I guess it's still within range.
I checked the GND connections on the level converter and the RS232-RS485, and they ARE both well connected to the ground ...

Next thing I'll try (probably later or tomorrow) is see if ant what is coming back from the epever...

Thanks again (I owe you one more ... :wink:)

PS.

I can't check your PCB reliable, but it seems there a some unconnected lines.
This because the PCB is single sided (I can't make GOOD double sided PCB's) and some connections are WIRED on the component side of the board.

just to be clear: after RE/DE of the master goes back to GND I would await to see the answer on A ...