iiOTESP32 Brasil
Modo de contingencia: las páginas públicas y las descargas siguen disponibles; el inicio de sesión, la comunidad y los datos del servidor pueden no estar disponibles.
Volver al catálogo de tutoriales
Ilustração: placa ESP32 ligada por fios coloridos a um módulo conversor RS-485 e, por um par trançado, a um controlador industrial em uma planta
Imagem ilustrativa gerada por IA
AvanzadoModbusLectura: 15 min

Modbus RTU mediante RS-485 en el ESP32

Lee registros Modbus RTU en el ESP32 sin biblioteca: control de DE/RE, CRC16, el silencio de 3,5 caracteres entre tramas y terminación de 120 ohmios.

Tecnologías:#ESP32#Modbus RTU#RS-485#MAX485#UART#CRC16

Requisitos de hardware:

  • Placa ESP32 ou ESP32-S3
  • Transceiver RS-485 (MAX3485 ou SP3485; MAX485 exige divisor no RO)
  • Dois resistores de 120 ohms para terminação
  • Par trançado, de preferência com blindagem
  • Um escravo Modbus RTU: multimedidor, inversor ou CLP

Por qué Modbus RTU todavía importa

Modbus RTU nació en 1979 y sigue saliendo de fábrica en medidores de energía, variadores de frecuencia, transmisores de presión y controladores de temperatura. No porque sea elegante, sino porque es suficientemente sencillo para un microcontrolador barato y suficientemente estable para funcionar veinte años dentro de un cuadro.

Leer estos equipos con un ESP32 cuesta pocos euros en componentes y resuelve el problema que motiva la mitad de los proyectos de IIoT: el dato existe, está en el cuadro y no llega a ninguna parte.

Este tutorial implementa el protocolo directamente, sin biblioteca. No por purismo, sino porque las bibliotecas ocultan precisamente los tres detalles que hacen fallar la comunicación y necesitarás entenderlos cuando el bus deje de responder.

RS-485 no es un puerto serie normal

El primer error conceptual es tratar RS-485 como si fuera UART con otro conector.

En una conexión serie común cada lado tiene su propio cable de transmisión. En RS-485 solo hay un par de cables, compartido por todos los dispositivos, y la información está en la diferencia de tensión entre ellos. Por eso recorre cientos de metros en un entorno lleno de motores y variadores, donde el serie normal no avanzaría ni diez.

La consecuencia es que solo un dispositivo puede hablar a la vez. No hay colisión detectable ni arbitraje. El maestro pregunta, el esclavo responde y durante ese intervalo nadie más habla. Todo el resto del artículo se deriva de esto.

El transceptor y el pin que lo decide todo

El MAX485 convierte la UART del ESP32 en el par diferencial. Tiene cuatro pines de control y dos hacen lo mismo con lógica invertida:

DI recibe lo que transmite el ESP32 y RO entrega lo que llega del bus. DE habilita el transmisor en nivel alto y RE habilita el receptor en nivel bajo.

Como nunca quieres transmitir y recibir al mismo tiempo, el patrón habitual es unir DE y RE al mismo GPIO. Alto significa hablar y bajo significa escuchar. Un cable menos y ninguna posibilidad de dejarlos activados por error.

code.cpp
cpp
1ESP32 GPIO17 (TX) -> DI
2ESP32 GPIO16 (RX) <- RO
3ESP32 GPIO4 -> DE e RE juntos
43V3 -> VCC
5GND -> GND

Usa un transceptor de 3,3 V, como MAX3485 o SP3485. El MAX485 clásico es de 5 V: muchas placas funcionan alimentadas a 3,3 V, pero RO puede entregar un nivel superior al que tolera el ESP32. Si usas el de 5 V, coloca un divisor en RO.

Error número uno: soltar DE demasiado pronto

Este es el fallo que más aparece y el más difícil de ver.

La tentación es escribir en el puerto serie y desactivar DE en la siguiente línea. Pero write no espera a que salgan los datos; solo los pone en la cola del búfer UART. Si bajas DE inmediatamente, el transmisor se apaga a mitad de la trama y el esclavo recibe un mensaje truncado sin responder nunca.

Lo correcto es esperar a que la UART se vacíe antes de soltarlo:

code.cpp
cpp
1void modbusEnviar(const uint8_t *quadro, size_t tamanho) {
2 digitalWrite(PINO_DE_RE, HIGH);
3 delayMicroseconds(50);
4 Serial2.write(quadro, tamanho);
5 Serial2.flush();
6 delayMicroseconds(50);
7 digitalWrite(PINO_DE_RE, LOW);
8}

flush es la línea importante: bloquea hasta que el último bit sale del registro de desplazamiento. Las dos esperas de 50 microsegundos dan tiempo al transceptor para conmutar; algunos necesitan unos microsegundos entre la activación y la estabilización de la salida.

Si el esclavo nunca responde y ya comprobaste el cableado y la dirección, casi siempre es esto.

El silencio de 3,5 caracteres

Modbus RTU no tiene marcador de inicio ni de fin de trama. Lo que delimita el mensaje es el tiempo de silencio: una pausa equivalente a 3,5 caracteres marca el final y una pausa superior a 1,5 caracteres en mitad de la trama invalida lo que venía antes.

Por eso el tiempo depende de la velocidad en baudios. En 8N1 cada carácter tarda 10 bits:

code.cpp
cpp
19600 bps 1 caractere = 1,042 ms silencio = 3,65 ms (calculado)
219200 bps 1 caractere = 0,521 ms silencio = 1,82 ms (calculado)
338400 bps 1 caractere = 0,260 ms silencio = 1,75 ms (fijo por norma)
4115200 bps 1 caractere = 0,087 ms silencio = 1,75 ms (fijo por norma)

Observa el cambio: por encima de 19200 la norma abandona el cálculo y fija 1,750 ms. El valor calculado sería menor que el tiempo de procesamiento típico de un esclavo; a 115200 sería 0,3 ms, demasiado poco para que muchos equipos reaccionen. Usar el valor calculado a esas velocidades produce una comunicación inestable con esclavos lentos.

En la práctica, respetarlo significa esperar el silencio antes de enviar la siguiente pregunta. Encadenar solicitudes sin pausa es la segunda causa más común de respuestas corruptas.

CRC16 y la trampa del orden de los bytes

Modbus usa CRC16 con polinomio 0xA001, calculado sobre todos los bytes de la trama. Son diez líneas:

code.cpp
cpp
1uint16_t modbusCRC(const uint8_t *dados, size_t tamanho) {
2 uint16_t crc = 0xFFFF;
3 for (size_t i = 0; i < tamanho; i++) {
4 crc ^= dados[i];
5 for (uint8_t bit = 0; bit < 8; bit++) {
6 if (crc & 0x0001) {
7 crc = (crc >> 1) ^ 0xA001;
8 } else {
9 crc >>= 1;
10 }
11 }
12 }
13 return crc;
14}

La trampa es que el CRC va en la trama con el byte menos significativo primero, a diferencia del resto del protocolo, que es big-endian. La dirección y la cantidad de registros llevan primero el byte alto; el CRC va invertido. Si te equivocas, la trama parece perfecta y nunca se acepta.

Construir una lectura de registros

La función 03 lee registros de retención, que es lo que utilizan el 90 % de los equipos. La trama tiene ocho bytes:

code.cpp
cpp
1uint8_t construirLeitura(uint8_t *quadro, uint8_t escravo,
2 uint16_t inicio, uint16_t quantidade) {
3 quadro[0] = escravo;
4 quadro[1] = 0x03;
5 quadro[2] = inicio >> 8;
6 quadro[3] = inicio & 0xFF;
7 quadro[4] = quantidade >> 8;
8 quadro[5] = quantidade & 0xFF;
9 uint16_t crc = modbusCRC(quadro, 6);
10 quadro[6] = crc & 0xFF;
11 quadro[7] = crc >> 8;
12 return 8;
13}

Observa el CRC en las dos últimas líneas: primero la parte baja y después la alta. Es la inversión explicada arriba.

La dirección del manual no es la dirección del protocolo

Esta es la tercera trampa y la que más tiempo consume a quien empieza.

El manual puede decir que la tensión de la fase A está en el registro 40001. En el protocolo ese registro es el número 0. La numeración 4xxxx es una convención de documentación, no lo que se envía en la trama.

La regla es restar 40001 al número del manual. 40001 se convierte en 0, 40011 en 10 y 40100 en 99.

Algunos fabricantes ya documentan direcciones en base cero y unos pocos mezclan las dos convenciones. Si la lectura tiene un valor plausible pero una magnitud equivocada, prueba a desplazar un registro a cada lado antes de sospechar del código.

Leer la respuesta con tiempo límite

La respuesta llega con la misma dirección, la misma función, la cantidad de bytes y los datos:

code.cpp
cpp
1bool modbusLer(uint8_t escravo, uint16_t inicio,
2 uint16_t quantidade, uint16_t *saida) {
3 uint8_t quadro[8];
4 uint8_t tamanho = construirLeitura(quadro, escravo, inicio, quantidade);
5 while (Serial2.available()) Serial2.read();
6 modbusEnviar(quadro, tamanho);
7 uint8_t esperado = 5 + quantidade * 2;
8 uint8_t resposta[64];
9 uint8_t recebidos = 0;
10 unsigned long limite = millis() + 300;
11 while (millis() < limite && recebidos < esperado) {
12 if (Serial2.available()) {
13 resposta[recebidos++] = Serial2.read();
14 }
15 }
16 if (recebidos < esperado) return false;
17 if (resposta[0] != escravo) return false;
18 if (resposta[1] & 0x80) return false;
19 uint16_t crc = modbusCRC(resposta, recebidos - 2);
20 if ((crc & 0xFF) != resposta[recebidos - 2]) return false;
21 if ((crc >> 8) != resposta[recebidos - 1]) return false;
22 for (uint16_t i = 0; i < quantidade; i++) {
23 saida[i] = (resposta[3 + i * 2] << 8) | resposta[4 + i * 2];
24 }
25 return true;
26}

Tres comprobaciones que muchas implementaciones omiten: limpiar el búfer antes de enviar descarta basura de lecturas anteriores; comprobar el bit 0x80 en la función detecta una respuesta de excepción, en la que el esclavo dice que la dirección no existe o el valor no es válido; y comprobar el CRC de la respuesta evita aceptar datos corrompidos por ruido, algo nada remoto en un cuadro eléctrico.

Terminación: los 120 ohmios no son opcionales

En un bus largo, la señal que llega al extremo se refleja y afecta al siguiente bit. El resultado es una comunicación que funciona en la mesa y falla en el cuadro, o funciona con un equipo y se detiene al conectar el segundo.

La corrección es una resistencia de 120 ohmios entre A y B en los dos extremos del bus; solo en las puntas, nunca en los dispositivos del medio. Muchos módulos llevan la resistencia soldada o un jumper; si el ESP32 está en mitad de la línea, quítalo.

También hace falta la polarización en reposo: cuando nadie transmite, la línea queda flotante y el receptor puede interpretar ruido como datos. Resistencias de 680 ohmios que lleven A al positivo y B a tierra lo resuelven. Algunos transceptores ya lo incorporan.

El par debe ser además trenzado. Dos cables paralelos anulan la ventaja de la señal diferencial, que consiste precisamente en captar la misma interferencia en ambos conductores.

Tutorial: de la mesa a la primera lectura

Paso 1: conectar el transceptor

Con todo desenergizado, conecta el módulo RS-485 según la tabla de la sección del transceptor. Confirma que DE y RE están unidos al mismo GPIO; en la mayoría de los módulos es un jumper o una pista ya preparada.

Alimenta el transceptor con 3,3 V, no con 5 V, salvo que hayas colocado un divisor en RO.

Paso 2: conectar el esclavo

Conecta A y B del módulo a A y B del equipo, respetando la polaridad. Los fabricantes usan nombres distintos: algunos dicen D+ y D-, otros A y B, y algunos invierten la convención.

Si no responde, intercambia A y B antes de tocar el código. Es gratis y resuelve muchos casos.

Conecta también la tierra de referencia entre ambos lados. RS-485 es diferencial, pero los dos lados tienen que estar dentro del mismo rango de tensión de modo común.

Paso 3: descubrir los parámetros del esclavo

Necesitas cuatro datos, todos en el manual o en la pantalla del equipo: dirección del esclavo (1 a 247), baud rate, paridad y el registro que quieres leer.

El ajuste más común es 9600 8N1, pero 19200 y la paridad par también aparecen bastante. Empieza por lo que indique el manual; si no hay manual, 9600 8N1 en la dirección 1 es la apuesta con más posibilidades.

Paso 4: configurar platformio.ini

Crea el archivo en la raíz del proyecto:

code.cpp
cpp
1[env:esp32-modbus]
2platform = espressif32
3board = esp32dev
4framework = arduino
5monitor_speed = 115200

No hay bibliotecas externas: el protocolo está implementado en el propio archivo.

Paso 5: ajustar los parámetros del código

En la parte superior del firmware, ajusta la dirección, los baudios y el registro con los valores obtenidos en el paso 3. Recuerda restar 40001 al número del manual.

Empieza leyendo un solo registro. Una cantidad grande en el primer intento dificulta separar un error de parámetros de un error de comunicación.

Paso 6: grabar y observar

Compila, graba y abre el monitor:

code.cpp
cpp
1pio run -t upload
2pio device monitor -b 115200

Una lectura correcta imprime el valor bruto. Compáralo con lo que muestra la pantalla del equipo: si el número coincide o es un múltiplo exacto de 10 o 100, has acertado el registro; el factor de escala está documentado en el manual.

Si el valor no tiene ninguna relación con la pantalla, el registro es incorrecto. Vuelve al paso 3.

Paso 7: interpretar la escala

Modbus transporta enteros de 16 bits. Un medidor que muestra 127,3 V suele devolver 1273, con el factor indicado en el manual.

Los valores superiores a 65535, como la energía acumulada, ocupan dos registros. Lee ambos y combínalos: la dirección menor suele ser la parte alta, pero algunos fabricantes lo invierten. Si el número salta de forma absurda, prueba el orden contrario.

Errores comunes y cómo resolverlos

El esclavo nunca responde. En este orden: invierte A y B; confirma que flush está antes de bajar DE; revisa dirección y baudios; comprueba la tierra de referencia.

Responde a veces y a veces no. Falta terminación o estás encadenando solicitudes sin respetar el silencio de 3,5 caracteres.

El CRC siempre es inválido. Casi seguro está invertido el orden de los bytes del CRC; la parte baja va primero.

Recibo una respuesta con el bit 0x80 en la función. Es una excepción del esclavo, no un error tuyo: el byte siguiente indica el motivo. El código 02 significa una dirección de registro inexistente, el caso más frecuente.

El valor leído no coincide con la pantalla. Registro o escala incorrectos. Comprueba el desplazamiento de 40001 y el factor del manual.

Funciona con un equipo y se detiene con dos. Terminación solo en los dos extremos y direcciones diferentes para cada esclavo.

Hay ruido cuando arranca el variador. Usa par trenzado apantallado, conecta la pantalla a tierra en un solo extremo y aleja el cable de los cables de potencia. Si persiste, baja la velocidad: 9600 tolera mucho más que 38400.

Ver más tutoriales técnicosIIoT ESP32 Brasil — Documentación práctica