Skip to content
Back to skills

Esp32 Modbus Rs485

ASecurity

Use when an ESP32 talks Modbus RTU over RS485 - wiring a MAX485, timing the DE pin, computing the CRC, or debugging a slave that never answers.

  • 16 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added November 7, 2026
ai-agentsnodetestingdebugginggitdocumentation

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned November 7, 2026

npx -y skills add Esmail-sarhadi/esp32-agent-skills --skill esp32-modbus-rs485 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Esp32 Modbus Rs485?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Esp32 Modbus Rs485
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/esmail-sarhadi-esp32-modbus-rs485/badge)](https://www.skillsdirectory.com/skills/esmail-sarhadi-esp32-modbus-rs485)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: esp32-modbus-rs485
description: Use when an ESP32 talks Modbus RTU over RS485 - wiring a MAX485, timing the DE pin, computing the CRC, or debugging a slave that never answers.
license: MIT
---

# Modbus RTU over RS485 on ESP32

Modbus is simple enough to look trivial and precise enough that every shortcut
produces silence. A slave that does not answer gives no error, so the timing and
the wiring have to be right by construction.

## The physical layer

- RS485 is a differential two-wire bus. `A` and `B` naming is not standardised:
  vendors swap them. If every frame times out, swap `A` and `B` before touching
  the code. This is the single most common cause of "no response".
- A MAX485 module has `DE` and `RE`; tie them together and drive the pin high to
  transmit, low to listen. If `DE` stays high, the transceiver holds the bus and
  no slave can answer.
- Long buses need 120 Ω termination at both physical ends, and a common ground
  between the nodes. Without the ground reference the differential pair drifts.

## The DE pin is a timing problem, not a wiring problem

The sequence has a trap: releasing `DE` before the last byte has left the UART
shift register truncates the frame, and the slave sees a CRC error.

```cpp
digitalWrite(DE_PIN, HIGH);          // take the bus
Serial1.write(frame, frameLen);
Serial1.flush();                     // wait until the last bit is out
digitalWrite(DE_PIN, LOW);           // release, now listen
```

`Serial1.write()` only fills the driver buffer. `Serial1.flush()` is the call
that waits for the wire. Dropping it works at 9600 on a fast board and fails at
115200, which is why it survives testing and breaks in the field.

## Frame timing

Modbus RTU delimits frames by silence: at least **3.5 character times** between
frames. At 9600 baud with 8N1, one character is 10 bits, about 1.04 ms, so the
gap is roughly 3.6 ms. At 115200 the gap is under 0.3 ms and a naive
`delay(10)` between frames is both too slow and, on a busy RTOS, not guaranteed.

- Do not send the next request until the response timeout has expired or the
  expected number of bytes arrived.
- The response timeout is the slave's problem, not yours: give it 100-300 ms
  before declaring a timeout.

## The CRC

CRC-16/MODBUS: initial value `0xFFFF`, polynomial `0xA001` (reflected), and the
result is appended **low byte first**.

```cpp
uint16_t modbusCrc(const uint8_t *buf, size_t len) {
  uint16_t crc = 0xFFFF;
  for (size_t i = 0; i < len; i++) {
    crc ^= buf[i];
    for (int bit = 0; bit < 8; bit++) {
      crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : (crc >> 1);
    }
  }
  return crc;
}
```

## Register addressing

The protocol addresses holding registers from 0. Device documentation usually
numbers them from 1 and calls the first one `40001`. `40001` in a datasheet is
register `0` on the wire. An off-by-one here returns a valid frame with the
wrong value, which is worse than a timeout because nothing looks broken.

## Rules for the agent

- Always include `Serial1.flush()` before dropping `DE`, and comment why.
- Never compute the CRC by hand in the request; use the function, and append it
  low byte first.
- When a slave does not answer, ask in this order: A/B swapped? DE pin driven?
  baud and parity match? slave address correct? Only then look at the frame.
- Do not poll faster than the device's documented cycle time. Some meters answer
  a request per second and silently drop the rest.

Files in this skill

  • LICENSE.txt857 B
  • SKILL.md3.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in ai-agents →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.