Opta RS485 bug: last byte of any frame is modified (CRC always wrong)

Hi,
I bought an Arduino Opta AFX00002 mainly to use the built‑in RS485 for industrial Modbus devices.

My first test was with an N4DSA02 module (Dallas DS18B20 ==> Modbus RTU converter).
After many attempts, the Opta never received any valid response.
So I started debugging the hardware and finally discovered a major issue with the Opta RS485 interface.

To be sure, I sniffed the RS485 line using a simple USB‑RS485 adapter.
Here is the minimal test code I used:

cpp

#include <ArduinoRS485.h>

void setup() {
  Serial.begin(115200);
  while (!Serial) {}

  Serial.println("Testing RS485 raw frame...");

  RS485.begin(9600); // 8N1
}

void loop() {
  uint8_t frame[] = {0xAA, 0xBB, 0xCC, 0xDD};

  RS485.noReceive();
  RS485.beginTransmission();
  RS485.write(frame, sizeof(frame));
  RS485.endTransmission();
  RS485.receive();

  delay(1000);
}

Expected frame on the RS485 line:

Code

AA BB CC DD

Actual frame sent by the Opta (sniffed):

Code

AA BB CC FF

The last byte is always modified by the Opta RS485 driver.
This happens with any frame, even non‑Modbus frames.
Because of this, the CRC of any Modbus RTU frame is always wrong, and no Modbus slave will ever respond.

This means the Opta RS485 interface is not sending raw UART data, but seems to apply some internal processing or CRC handling that cannot be disabled.

Conclusion

The built‑in RS485 of the Opta cannot currently be used for Modbus RTU, because the last byte of every frame is changed.

Workaround

Using Serial + an external UART‑to‑RS485 converter works correctly and sends the proper bytes.

Questions

  • Is this a known issue with the Opta RS485 driver?

  • Can someone from Arduino confirm this behavior?

  • Is there a fix planned or a way to disable the internal processing?

Thanks.

I wait your reply

Try to add
RS485.flush();
or
delay(1);
before RS485.endTransmission();

Maybe safer to both flush and delay one byte time; in that sequence.

@ise_ch, at the bottom of this topic are a number of links to topics with possibly the same issue. I suggest that you check them out.

it’s not a timing error

I tested again with the following code:

#include <ArduinoRS485.h>


void setup() {
  Serial.begin(115200);
  while (!Serial) {}

  Serial.println("Testing RS485 flush/delay...");
  RS485.begin(9600);
}

void loop() {

  //uint8_t frame[] = {0xAA, 0xBB, 0xCC, 0xDD};
  uint8_t frame[] = {0x01,0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A};

  RS485.noReceive();
  RS485.beginTransmission();
  RS485.write(frame, sizeof(frame));


  RS485.flush();   
  delay(1);        


  RS485.endTransmission();
  RS485.receive();


  delay(1000);

}

When I send the simple test frame AA BB CC DD, I sniff AA BB CC DD on the RS485 line, so timing is fine.

But when I send the correct Modbus RTU frame:

Code

01 03 00 00 00 01 84 0A

I sniff this on the line:

Code

01 03 00 00 00 01 84 CA

So the last byte is still modified, even with flush() and delay(1).

If I use only RS485.flush() or only delay(1), it still does not work**.** The last byte of the frame is still changed by the Opta.

This confirms it is not a timing problem

So you resolved your OP and it was timing issue.

What is your modbus device protocol telling about parity?

You misunderstood my point.

The simple test frame AA BB CC DD works only because it has no CRC and the last byte does not matter for any protocol.
This does not mean the timing issue is solved.

When I send a real Modbus RTU frame with CRC:

Code

01 03 00 00 00 01 84 0A

I sniff this on the RS485 line:

Code

01 03 00 00 00 01 84 CA

The last byte is still modified by the Opta RS485 driver, even with flush() and even with delay(1).
So the CRC is always wrong, and the Modbus slave will never reply.

This is not a parity issue.
Parity is correct, baudrate is correct, wiring is correct.
The Opta simply does not transmit the last byte as sent.

So the original problem is not resolved.
It is not a timing problem, but a frame corruption problem on the last byte.

But before it gave you FF instead of DD.

What's the outcome with this?
RS485.begin(9600, SERIAL_8E1);

already tried :winking_face_with_tongue:

Try again. Outcome should be different.

I tested again with a very simple 4‑byte frame:

Code

11 22 33 44

And what I sniff on the RS485 line is:

Code

11 22 33 F4

So the last byte is still corrupted by the Opta RS485 driver, even without Modbus, without CRC, without parity issues, and even with flush() and delay().
This confirms that the problem is not timing‑related or protocol‑related, but a real frame corruption issue on the last transmitted byte.

Here is the test code (changing parity does not improve anything):

#include <ArduinoRS485.h>

void setup() {
  Serial.begin(115200);
  while (!Serial) {}

  Serial.println("Testing RS485 flush/delay...");
  RS485.begin(9600, SERIAL_8E1);
}

void loop() {
  //uint8_t frame[] = {0xAA, 0xBB};
  //uint8_t frame[] = {0xAA, 0xBB, 0xCC, 0xDD};
  uint8_t frame[] = {0x11, 0x22, 0x33, 0x44};
  //uint8_t frame[] = {0xAA,0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
  //uint8_t frame[] = {0x01,0x03, 0x00, 0x00, 0x00, 0x01, 0x84, 0x0A};

  RS485.noReceive();
  RS485.beginTransmission();
  RS485.write(frame, sizeof(frame));

  RS485.flush();
  delay(1);

  RS485.endTransmission();
  RS485.receive();

  delay(1000);
}

Every post different data..
What is your sniffer code? Is it set for 8E1?

The idea of flush() was not that useful; no offence intended to @kmin. This is endTransmission()

void RS485Class::endTransmission()
{
  _serial->flush();

  if (_dePin > -1) {
    if (_postdelay) delayMicroseconds(_postdelay);
    digitalWrite(_dePin, LOW);
  }

  _transmisionBegun = false;
}

It already does the flush. The idea of the delay was useful but is can be seen in above code there is already something like that; one just needs to configure it with setDelays().

@ise_ch, you can look at the tutorial (https://docs.arduino.cc/tutorials/opta/getting-started-with-rs485/) and try that.

Note:
I'm not familiar with the Opta so wonder if I can be of more assistance.

No. same parity, same bit stop, same baudrate, all same

Try little longer delay:
delayMicroseconds(1600);

If it doesn't resolve the issue, test what you get with the last byte 00 or FF.

Based on:

https://opta.findernet.com/it/tutorial/getting-started-rs-485

	constexpr auto bitduration{ 1.f / baudrate };
	constexpr auto wordlen{ 9.6f };  // OR 10.0f depending on the channel configuration
	constexpr auto preDelayBR{ bitduration * wordlen * 3.5f * 1e6 };
	constexpr auto postDelayBR{ bitduration * wordlen * 3.5f * 1e6 };

the pre/post delay values should be 3646 µs for a speed of 9600 bps 8N1.

ok, just another code example to explain that this issue is not timing related, and not related to Modbus protocol timing.

Here is my test code:

#include <ArduinoRS485.h>

void setup() {
Serial.begin(115200);
while (!Serial) {}

Serial.println("Rotating RS485 frame test...");
RS485.begin(9600, SERIAL_8N1);
}

void sendFrame(const uint8_t* frame, size_t len) {
RS485.noReceive();
RS485.beginTransmission();
RS485.write(frame, len);
RS485.flush();
delay(1);
RS485.endTransmission();
RS485.receive();
}

void loop() {
static uint8_t frame[16];

// Fill frame with incremental pattern
for (int i = 0; i < sizeof(frame); i++) {
frame[i] = i * 0x11;   // 00, 11, 22, 33, 44, 55...
}

// Test frames from 1 to 16 bytes
for (int len = 1; len <= 16; len++) {
Serial.print("Sending ");
Serial.print(len);
Serial.println(" bytes...");
sendFrame(frame, len);
delay(200);
}

delay(5000);
}


Expected output on RS485 line:

00 00 11 00 11 22 00 11 22 33 00 11 22 33 44 00 11 22 33 44 55 00 11 22 33 44 55 66 00 11 22 33 44 55 66 77 00 11 22 33 44 55 66 77 88 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

Actual received output (10 runs):

FC 00 F1 00 11 F2 00 11 22 FB 00 11 22 33 FF 00 11 22 33 44 FD 00 11 22 33 44 55 FE 00 11 22 33 44 55 66 77 00 11 22 33 44 55 66 77 FF 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

FC 00 F9 00 11 FA 00 11 22 FB 00 11 22 33 FC 00 11 22 33 44 FF 00 11 22 33 44 55 FF 00 11 22 33 44 55 66 FF 00 11 22 33 44 55 66 77 FF 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 FF 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

FC 00 F1 00 11 FA 00 11 22 FF 00 11 22 33 FF 00 11 22 33 44 55 00 11 22 33 44 55 66 00 11 22 33 44 55 66 FF 00 11 22 33 44 55 66 77 88 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

FC 00 F9 00 11 FA 00 11 22 FB 00 11 22 33 FC 00 11 22 33 44 FD 00 11 22 33 44 55 FF 00 11 22 33 44 55 66 FF 00 11 22 33 44 55 66 77 FF 00 11 22 33 44 55 66 77 88 FF 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

FC 00 FD 00 11 FA 00 11 22 FF 00 11 22 33 FC 00 11 22 33 44 FF 00 11 22 33 44 55 66 00 11 22 33 44 55 66 77 00 11 22 33 44 55 66 77 88 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

F0 00 FD 00 11 FA 00 11 22 FF 00 11 22 33 FC 00 11 22 33 44 FF 00 11 22 33 44 55 FF 00 11 22 33 44 55 66 FF 00 11 22 33 44 55 66 77 88 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

F8 00 F1 00 11 FE 00 11 22 FB 00 11 22 33 FF 00 11 22 33 44 FD 00 11 22 33 44 55 FF 00 11 22 33 44 55 66 77 00 11 22 33 44 55 66 77 88 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

F8 00 F9 00 11 FA 00 11 22 FB 00 11 22 33 FE 00 11 22 33 44 FF 00 11 22 33 44 55 FE 00 11 22 33 44 55 66 FF 00 11 22 33 44 55 66 77 C8 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

F0 00 FD 00 11 F2 00 11 22 FB 00 11 22 33 FF 00 11 22 33 44 FF 00 11 22 33 44 55 FF 00 11 22 33 44 55 66 FF 00 11 22 33 44 55 66 77 FF 00 11 22 33 44 55 66 77 88 FF 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA FF 00 11 22 33 44 55 66 77 88 99 AA BB CC 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

FC 00 F9 00 11 FF 00 11 22 FB 00 11 22 33 FC 00 11 22 33 44 FD 00 11 22 33 44 55 66 00 11 22 33 44 55 66 FF 00 11 22 33 44 55 66 77 88 00 11 22 33 44 55 66 77 88 99 00 11 22 33 44 55 66 77 88 99 AA 00 11 22 33 44 55 66 77 88 99 AA BB 00 11 22 33 44 55 66 77 88 99 AA BB FF 00 11 22 33 44 55 66 77 88 99 AA BB CC DD 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

Observations

  • The bug appears systematically on the first byte for short frames

  • Then on the last byte for medium frames

  • After ~13 bytes, the output becomes stable and correct

  • This behaviour is identical across more than 100 runs (see upper a sample of 10 results)

  • This is not related to Modbus timing, to user delays (flush(), delay()), to parity (I tested 8N1 and 8E1), to baudrate, to the sniffer (it matches 8N1 exactly)

Next step:
I will test the Opta tutorial method:

https://docs.arduino.cc/tutorials/opta/getting-started-with-rs485/

But I am quite sure timing is not the solution, because the corruption happens inside the frame, not between frames.

If you have corruption all around, not just the last bits like you presented before, verify your wiring and termination resistors.

Root Cause and Solution

I investigated a recurring issue where RS‑485 frames sent from an Arduino Opta were sometimes corrupted. The corruption appeared on the first or last byte depending on frame length, even with correct wiring and sniffer configuration (9600 baud, 8N1).

1. Diagnostic Summary

Simple frame tests

  • 1 byte → OK
  • 4 bytes → OK
  • 10 bytes → last byte corrupted
  • 16 bytes → last byte corrupted

Pattern tests

Incremental, reversed, and fixed patterns all showed the same behavior:
corruption always occurred on the last byte when the frame was long enough.

Timing tests

By adding delays after RS485.flush(), I found:

Delay Result
1100 µs OK
900 µs OK
800 µs OK
700 µs Corrupted
500 µs Corrupted

This clearly shows that the Opta needs ~800–1000 µs after flush() before calling endTransmission() at 9600 baud.

2. Root Cause

###:red_exclamation_mark: RS485.flush() is mandatory, but NOT sufficient.
flush() only ensures that the software UART buffer is empty,
but does NOT guarantee that the last byte has physically left the RS‑485 transceiver.

###:red_exclamation_mark: RS485.endTransmission() disables the DE line immediately,

even if the last byte is still being transmitted on the wire.
This results in the last byte being cut off or distorted, causing corruption.
This issue is not related to:

  • Modbus timing
  • parity
  • wiring
  • sniffer configuration
  • user code
  • frame length changes

It is purely a hardware timing issue:
DE is disabled too early unless a proper post‑delay is added.

3. Arduino Documentation Reference

Arduino mentions the need for pre‑delay and post‑delay here:
:link: https://docs.arduino.cc/tutorials/opta/getting-started-with-rs485/

However:

:red_exclamation_mark: The documentation does NOT explicitly state that

flush() is required AND that flush() alone is not enough.

:red_exclamation_mark: The documentation uses Modbus RTU timing (3.5 characters),

but the Opta requires at least 1 full character time to avoid corruption.
My measurements confirm this precisely.

4. Correct Solution (Baudrate‑Dependent Post‑Delay)

The correct post‑delay is the time required to transmit one full RS‑485 character:
postDelay=1baudrate×10 bits×10^6
Examples:

Baudrate Required postDelay
9600 ~1040 µs
19200 ~520 µs
38400 ~260 µs
115200 ~86 µs

:check_mark: Adding this post‑delay completely eliminates all corruption.

5. Implementation

constexpr uint32_t baudrate = 9600;

float bitduration = 1.0f / baudrate;
float wordlen = 10.0f; // 1 start + 8 data + 1 stop
uint32_t postDelay = bitduration * wordlen * 1e6;

RS485.begin(baudrate, SERIAL_8N1);

// flush() is mandatory
// postDelay is mandatory
RS485.setDelays(0, postDelay);

6. Final Result

After applying the calculated post‑delay:

:check_mark: No more corrupted bytes
:check_mark: All frame lengths are stable
:check_mark: All patterns transmit correctly
:check_mark: Behavior is consistent across baudrates

Final Statement

RS485.flush() is required, but it does NOT ensure the last byte has physically left the RS‑485 line.
A baudrate‑dependent post‑delay is mandatory to prevent DE from being disabled too early.

With the correct post‑delay (≈1 character time), RS‑485 transmission on the Opta becomes fully reliable.

Thanks for your help and for guiding me toward this conclusion.

Glad that you got it sorted.

Indeed. You need the time to transmit the last byte (e.g. 8 bits + start/stop/parity) before disabling the data output of the transmitter.

A general reference: Gammon Forum : Electronics : Microprocessors : RS485 communications.

Nice that you resolved it :bottle_with_popping_cork:.
I wonder why you kept insisting that it's not time related and that my suggestion for 1600us delay (1040us +50% margin) didn't work for you.