Community project

ESP32 RS485 Temperature Monitor

Photo of ESP32 RS485 Temperature Monitor
Generated with AI

Pasindu Vidushan

Published November 3, 2026

This project builds a multi-sensor temperature and humidity monitoring system using a Raspberry Pi Pico connected to Modbus RS485 temperature and humidity probes over a shared bus. The guide provides a wiring diagram, complete parts list, and step-by-step assembly instructions that keep the three probes isolated during setup before connecting them to the shared RS485 A and B lines.

The included firmware implements Modbus RTU protocol with CRC validation to read sensor data from each probe using unique addresses on the same bus. Builders will learn how to wire the SP3485 RS485 transceiver module to the Pico, configure the three sensors with different Modbus addresses, and verify all connections before powering on the complete system.

Wiring diagram

Loading diagram…

Gather all the parts

QtyComponent
1

5 V Modbus RS485 temperature probe

A sealed temperature probe that reports its measurement over the two-wire RS485 cable.

1

5 V Modbus RS485 temperature and humidity probe

A wired probe that reports air temperature and humidity over the shared RS485 cable.

1

5 V Modbus RS485 temperature and humidity probe

A wired probe that reports air temperature and humidity over the shared RS485 cable.

1

SP3485 RS485 Transceiver Module

3.3V half-duplex RS485 transceiver module based on the SP3485 IC. Supports data rates up to 10 Mbps (well above 115200 bps), natively 3.3V logic compatible for direct connection to ESP32 UART pins. Features DE (Driver Enable) and RE (Receiver Enable) direction-control pins, combined as DE/RE for half-duplex control. Module pin DI (Data Input) connects to ESP32 TX; module pin RO (Receiver Output) connects to ESP32 RX. Provides A/B differential bus lines. Suitable for Modbus RTU over RS485, including Danfoss/Carel industrial controllers. No level-shifting required when used with ESP32.

Assemble it in 6 steps

1. Keep the three probes separate at first

Do not join the three probes to the same A and B wires until they have different device addresses. This design uses address 1 for humidity probe 1, address 2 for humidity probe 2, and address 3 for the temperature probe, so the Pico can talk to one probe at a time.

  • RS485 is like a shared conversation: two probes with the same address will both try to reply and the readings will fail.
  • Do not connect all three probes if they all still use address 1; they can interfere with each other. Their address is normally changed using the probe supplier's configuration instructions or tool.

2. Power the RS485 converter

With the Pico W unplugged, connect the SP3485 converter VCC pin to the Pico 3V3(OUT) pin and its GND pin to any Pico GND pin. The converter must use 3.3 V because its small logic pins connect directly to the Pico.

  • Use a 3.3 V SP3485 module, not the blue 5 V MAX485 board shown in the earlier photo.
  • Do not put 5 V into the SP3485 VCC pin; swapped or excessive power can damage the converter.

3. Connect the Pico data wires

Connect Pico GP12 to converter TX or DI, Pico GP13 to converter RX or RO, and Pico GP14 to converter DE. Join the converter RE pin directly to its DE pin with a short jumper. GP12 sends a question, GP13 receives the reply, and GP14 chooses whether the converter is talking or listening.

  • Follow the printed labels on your converter: TX means DI and RX means RO on some boards.
  • Do not connect the converter TX pin to Pico GP13 or the converter RX pin to GP12; crossing these two prevents the Pico from receiving replies.

4. Make the shared A and B bus

Run one wire from converter A to the A wire of humidity probe 1, then continue that same A wire to humidity probe 2 and the temperature probe. Repeat with converter B connected to every probe B wire. Keep A with A and B with B all the way along the cable.

  • For a long cable, use a twisted pair for A and B and place the probes in a chain rather than making long branches.
  • Do not swap A and B at one probe; that probe will not answer. Some manufacturers reverse the A/B names, so if none answer after checking power and addresses, swap the pair together at the converter end.

5. Power all three probes

Connect each probe VCC wire to Pico VBUS, the 5 V pin supplied by USB, and connect each probe GND wire to Pico GND. All three grounds must be connected together so the signals have the same reference.

  • Use a small terminal block or a power distribution strip to split the one VBUS and GND connection neatly to all three probes.
  • Only use the Pico USB 5 V supply if every probe is marked 5 V. A probe marked 9–24 V, 12–24 V, or similar needs its own matching supply and must not be connected to Pico VBUS.

6. Check before switching on

Check that the SP3485 is on 3.3 V, each probe is on its marked supply voltage, every probe A joins the same A wire, every B joins the same B wire, and all grounds are joined. Then plug the Pico W into USB.

  • The program prints one reading from each probe every three seconds after it is deployed.
  • Never connect the blue 5 V MAX485 board's RO output directly to Pico GP13; it can put 5 V on a Pico input and damage it.

Review all connections

1. Connections between "rs485_converter" and "Raspberry Pi Pico"

Functionrs485_converterRaspberry Pi Pico
powerVCC → 3.3 V power for the SP3485 converter3V3
groundGND → common groundGND
uartTX → Pico UART transmit to converter DIGPIO 12
uartRX → converter RO to Pico UART receiveGPIO 13
digitalDE → RS485 transmit/receive direction controlGPIO 14
dataA → 5 V Modbus RS485 temperature and humidity probe AEXT
dataB → 5 V Modbus RS485 temperature and humidity probe BEXT
digitalRE → SP3485 RS485 Transceiver Module DEEXT

2. Connections between "rs485_humidity_sensor_1" and "Raspberry Pi Pico"

Functionrs485_humidity_sensor_1Raspberry Pi Pico
powerVCC → 5 V probe power from Pico USB VBUS5V
groundGND → common groundGND

3. Connections between "rs485_humidity_sensor_2" and "Raspberry Pi Pico"

Functionrs485_humidity_sensor_2Raspberry Pi Pico
powerVCC → 5 V probe power from Pico USB VBUS5V
groundGND → common groundGND
dataA → SP3485 RS485 Transceiver Module AEXT
dataB → SP3485 RS485 Transceiver Module BEXT

4. Connections between "rs485_temperature_sensor" and "Raspberry Pi Pico"

Functionrs485_temperature_sensorRaspberry Pi Pico
powerVCC → 5 V probe power from Pico USB VBUS5V
groundGND → common groundGND
dataA → SP3485 RS485 Transceiver Module AEXT
dataB → SP3485 RS485 Transceiver Module BEXT

Deploy the firmware

#include <Arduino.h>

constexpr uint8_t RS485_TX_PIN = 12;
constexpr uint8_t RS485_RX_PIN = 13;
constexpr uint8_t RS485_DIR_PIN = 14;
constexpr uint32_t MODBUS_BAUD = 9600;

// Each probe on one RS485 bus needs a different Modbus address.
constexpr uint8_t HUMIDITY_SENSOR_1_ADDRESS = 1;
constexpr uint8_t HUMIDITY_SENSOR_2_ADDRESS = 2;
constexpr uint8_t TEMPERATURE_SENSOR_ADDRESS = 3;

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

bool readHoldingRegisters(uint8_t address, uint16_t startRegister, uint16_t *values, uint8_t count) {
  if (count == 0 || count > 2) return false;

  uint8_t request[8] = {address, 0x03, static_cast<uint8_t>(startRegister >> 8),
                        static_cast<uint8_t>(startRegister), 0x00, count, 0x00, 0x00};
  const uint16_t requestCrc = modbusCrc(request, 6);
  request[6] = static_cast<uint8_t>(requestCrc);
  request[7] = static_cast<uint8_t>(requestCrc >> 8);

  while (Serial1.available()) Serial1.read();
  digitalWrite(RS485_DIR_PIN, HIGH);
  Serial1.write(request, sizeof(request));
  Serial1.flush();
  delayMicroseconds(250);
  digitalWrite(RS485_DIR_PIN, LOW);

  const uint8_t expectedLength = 5 + count * 2;
  uint8_t response[9];
  uint8_t received = 0;
  const uint32_t deadline = millis() + 350;
  while (received < expectedLength && millis() < deadline) {
    if (Serial1.available()) response[received++] = static_cast<uint8_t>(Serial1.read());
    else delay(1);
  }

  if (received != expectedLength || response[0] != address || response[1] != 0x03 || response[2] != count * 2) return false;
  const uint16_t responseCrc = static_cast<uint16_t>(response[expectedLength - 2]) |
                               (static_cast<uint16_t>(response[expectedLength - 1]) << 8);
  if (modbusCrc(response, expectedLength - 2) != responseCrc) return false;

  for (uint8_t i = 0; i < count; ++i) {
    values[i] = (static_cast<uint16_t>(response[3 + i * 2]) << 8) | response[4 + i * 2];
  }
  return true;
}

void printHumidityProbe(const char *name, uint8_t address) {
  // Generic Modbus probe map: register 0 = humidity x10, register 1 = temperature x10.
  uint16_t values[2];
  if (!readHoldingRegisters(address, 0, values, 2)) {
    Serial.print(name);
    Serial.println(": no answer. Check its address and A/B wires.");
    return;
  }
  const float humidity = values[0] / 10.0f;
  const float temperature = static_cast<int16_t>(values[1]) / 10.0f;
  Serial.print(name);
  Serial.print(": ");
  Serial.print(temperature, 1);
  Serial.print(" C, ");
  Serial.print(humidity, 1);
  Serial.println(" % RH");
}

void printTemperatureProbe() {
  // Generic temperature-probe map: register 0 = signed temperature x10.
  uint16_t value;
  if (!readHoldingRegisters(TEMPERATURE_SENSOR_ADDRESS, 0, &value, 1)) {
    Serial.println("Temperature probe: no answer. Check its address and A/B wires.");
    return;
  }
  const float temperature = static_cast<int16_t>(value) / 10.0f;
  Serial.print("Temperature probe: ");
  Serial.print(temperature, 1);
  Serial.println(" C");
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  pinMode(RS485_DIR_PIN, OUTPUT);
  digitalWrite(RS485_DIR_PIN, LOW);
  Serial1.setTX(RS485_TX_PIN);
  Serial1.setRX(RS485_RX_PIN);
  Serial1.begin(MODBUS_BAUD);
  Serial.println("Three-probe RS485 monitor started.");
}

void loop() {
  printHumidityProbe("Humidity probe 1", HUMIDITY_SENSOR_1_ADDRESS);
  printHumidityProbe("Humidity probe 2", HUMIDITY_SENSOR_2_ADDRESS);
  printTemperatureProbe();
  Serial.println();
  delay(3000);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace, diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik