INTRODUCCIÓN
La comunicación RS485 es un estándar de transmisión serial ampliamente utilizado en sistemas industriales y electrónicos debido a su alta estabilidad y resistencia al ruido eléctrico. Funciona mediante comunicación diferencial usando dos líneas llamadas A y B, donde la información se transmite a través de la diferencia de voltaje entre ambas señales. Esto permite realizar comunicaciones confiables a largas distancias y conectar varios dispositivos en un mismo bus. Generalmente se utiliza junto a módulos como el MAX485, que convierten las señales UART del microcontrolador en señales RS485. Gracias a sus ventajas, RS485 es muy empleado en automatización, redes de sensores, máquinas vending y sistemas de control industrial.
ESPECIFICACIONES TÉCNICAS

- Microcontrolador: ATmega328P-AU @ 16 MHz
- Alimentación: Entrada 12- 24 VDC
- Comunicación: RS485 mediante MAX485ESA+T
- Entradas: 6 digitales, 2 analógicas
- Visualización: LCD 16×2 con PCF8574T y Display 7 segmentos con 74HC595D
- Funciones:
-
- Comunicación RS485
- Lectura de señales
- Visualización y diagnóstico
- Programación ISP
-
CODIGOS PARA PRUEBAS

EJECICIO 1: ENVIO DE DATOS ANALOGICOS AMBOS(cada tarjeta envía y recibe a la vez)
CODIGO MAESTRO
// ============================================
// COMUNICACIÓN BIDIRECCIONAL SIMÉTRICA
// Ambos dispositivos envían y reciben
// Muestran: Línea 1 = lo que ENVÍAN
// Línea 2 = lo que RECIBEN
// ============================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Configuración
#define TIEMPO_TURNO 250 // 250ms cada turno (2 turnos = 500ms ciclo)
#define PIN_ANALOGO A6
// Variables
unsigned long lastTurnTime = 0;
bool miTurno = true; // true = yo envío, false = yo recibo
int miValor = 0;
int ultimoValorRecibido = -1;
int ultimoValorEnviado = -1;
// Buffer para recibir datos
String incomingData = "";
void setup() {
// Inicializar RS485
Serial.begin(19200);
// Inicializar LCD
lcd.init();
lcd.backlight();
lcd.clear();
// Determinar quién empieza (aleatorio o por dirección)
// Usamos el tiempo de inicio para evitar que ambos empiecen igual
delay(random(100, 300));
lcd.setCursor(0, 0);
lcd.print("Envio: ");
lcd.setCursor(0, 1);
lcd.print("Recibo: ");
// Mostrar ID único (último dígito de la dirección MAC o random)
int miID = random(1, 100);
lcd.setCursor(8, 0);
lcd.print("ID:");
lcd.print(miID);
delay(1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Yo envio:");
lcd.setCursor(0, 1);
lcd.print("Recibo:");
}
void loop() {
unsigned long currentTime = millis();
// ============================================
// CAMBIO DE TURNO CADA TIEMPO_TURNO
// ============================================
if ((currentTime - lastTurnTime) >= TIEMPO_TURNO) {
lastTurnTime = currentTime;
miTurno = !miTurno; // Alternar turno
if (miTurno) {
// ¡Mi turno de ENVIAR!
enviarDatos();
}
}
// ============================================
// SI NO ES MI TURNO, ESCUCHO Y RECIBO
// ============================================
if (!miTurno) {
recibirDatos();
}
}
// ============================================
// ENVIAR MIS DATOS POR RS485
// ============================================
void enviarDatos() {
// Leer mi valor analógico
miValor = analogRead(PIN_ANALOGO);
// Mostrar en LCD lo que estoy enviando
if (miValor != ultimoValorEnviado) {
ultimoValorEnviado = miValor;
lcd.setCursor(9, 0);
lcd.print(" ");
lcd.setCursor(9, 0);
lcd.print(miValor);
}
// Enviar por RS485 con formato: "VALOR:512\n"
Serial.print("VALOR:");
Serial.print(miValor);
Serial.println();
// Pequeña pausa para asegurar envío
delay(10);
}
// ============================================
// RECIBIR DATOS DEL OTRO DISPOSITIVO
// ============================================
void recibirDatos() {
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
incomingData.trim();
if (incomingData.startsWith("VALOR:")) {
int valorRecibido = incomingData.substring(6).toInt();
// Mostrar en LCD lo que recibí
if (valorRecibido != ultimoValorRecibido) {
ultimoValorRecibido = valorRecibido;
lcd.setCursor(8, 1);
lcd.print(" ");
lcd.setCursor(8, 1);
lcd.print(valorRecibido);
}
}
incomingData = "";
}
else if (c != '\r') {
incomingData += c;
}
}
}
CODIGO ESCLAVO
// ============================================
// COMUNICACIÓN BIDIRECCIONAL SIMÉTRICA
// Ambos dispositivos envían y reciben
// Muestran: Línea 1 = lo que ENVÍAN
// Línea 2 = lo que RECIBEN
// ============================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Configuración
#define TIEMPO_TURNO 250 // 250ms cada turno (2 turnos = 500ms ciclo)
#define PIN_ANALOGO A6
// Variables
unsigned long lastTurnTime = 0;
bool miTurno = true; // true = yo envío, false = yo recibo
int miValor = 0;
int ultimoValorRecibido = -1;
int ultimoValorEnviado = -1;
// Buffer para recibir datos
String incomingData = "";
void setup() {
// Inicializar RS485
Serial.begin(19200);
// Inicializar LCD
lcd.init();
lcd.backlight();
lcd.clear();
// Determinar quién empieza (aleatorio o por dirección)
// Usamos el tiempo de inicio para evitar que ambos empiecen igual
delay(random(100, 300));
lcd.setCursor(0, 0);
lcd.print("Envio: ");
lcd.setCursor(0, 1);
lcd.print("Recibo: ");
// Mostrar ID único (último dígito de la dirección MAC o random)
int miID = random(1, 100);
lcd.setCursor(8, 0);
lcd.print("ID:");
lcd.print(miID);
delay(1000);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Yo envio:");
lcd.setCursor(0, 1);
lcd.print("Recibo:");
}
void loop() {
unsigned long currentTime = millis();
// ============================================
// CAMBIO DE TURNO CADA TIEMPO_TURNO
// ============================================
if ((currentTime - lastTurnTime) >= TIEMPO_TURNO) {
lastTurnTime = currentTime;
miTurno = !miTurno; // Alternar turno
if (miTurno) {
// ¡Mi turno de ENVIAR!
enviarDatos();
}
}
// ============================================
// SI NO ES MI TURNO, ESCUCHO Y RECIBO
// ============================================
if (!miTurno) {
recibirDatos();
}
}
// ============================================
// ENVIAR MIS DATOS POR RS485
// ============================================
void enviarDatos() {
// Leer mi valor analógico
miValor = analogRead(PIN_ANALOGO);
// Mostrar en LCD lo que estoy enviando
if (miValor != ultimoValorEnviado) {
ultimoValorEnviado = miValor;
lcd.setCursor(9, 0);
lcd.print(" ");
lcd.setCursor(9, 0);
lcd.print(miValor);
}
// Enviar por RS485 con formato: "VALOR:512\n"
Serial.print("VALOR:");
Serial.print(miValor);
Serial.println();
// Pequeña pausa para asegurar envío
delay(10);
}
// ============================================
// RECIBIR DATOS DEL OTRO DISPOSITIVO
// ============================================
void recibirDatos() {
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
incomingData.trim();
if (incomingData.startsWith("VALOR:")) {
int valorRecibido = incomingData.substring(6).toInt();
// Mostrar en LCD lo que recibí
if (valorRecibido != ultimoValorRecibido) {
ultimoValorRecibido = valorRecibido;
lcd.setCursor(8, 1);
lcd.print(" ");
lcd.setCursor(8, 1);
lcd.print(valorRecibido);
}
}
incomingData = "";
}
else if (c != '\r') {
incomingData += c;
}
}
}
EJERCICIO 2: COMUNICACIÓN MAESTRO-ESCLAVO CON RETROALIMENTACIÓN
CODIGO MAESTRO
// ============================================
// MAESTRO - RECIBE BOTONES DEL ESCLAVO
// MUESTRA EN DISPLAY 7 SEGMENTOS (74HC595)
// CONFIRMA RECEPCIÓN AL ESCLAVO
// MUESTRA ESTADO EN LCD I2C
// ============================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// LCD I2C (probar 0x27 o 0x3F)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Pines del 74HC595
#define DS_PIN 11
#define SHCP_PIN 13
#define STCP_PIN 12
// Variables
int ultimoValorMostrado = -1;
unsigned long lastReceiveTime = 0;
unsigned long lastHeartbeatTime = 0;
unsigned long lastLcdUpdate = 0;
// Variables para LCD
int ultimoRecibido = -1;
String ultimoRemitente = "";
String estadoComunicacion = "Esperando...";
int paquetesRecibidos = 0;
// Buffer para comunicación
String incomingData = "";
// Tabla de segmentos para números 0-9 (cátodo común)
byte digitPatterns[] = {
0b11111100, // 0
0b01100000, // 1
0b11011010, // 2
0b11110010, // 3
0b01100110, // 4
0b10110110, // 5
0b10111110, // 6
0b11100000, // 7
0b11111110, // 8
0b11110110 // 9
};
void setup() {
// Inicializar RS485
Serial.begin(19200);
// Inicializar LCD
lcd.init();
lcd.backlight();
lcd.clear();
// Configurar pines del 74HC595
pinMode(DS_PIN, OUTPUT);
pinMode(SHCP_PIN, OUTPUT);
pinMode(STCP_PIN, OUTPUT);
// Mostrar pantalla de inicio
lcd.setCursor(0, 0);
lcd.print("=== MAESTRO ===");
lcd.setCursor(0, 1);
lcd.print("Iniciando...");
delay(2000);
lcd.clear();
// Configurar LCD con encabezados fijos
lcd.setCursor(0, 0);
lcd.print("Recibido: ");
lcd.setCursor(0, 1);
lcd.print("Estado: ");
// Mostrar 0 en display 7 segmentos
mostrarNumero(0);
delay(500);
mostrarNumero(8);
delay(500);
mostrarNumero(0);
// Enviar mensaje de inicio
Serial.println("SISTEMA_MAESTRO_LISTO");
}
void loop() {
// ========================================
// 1. RECIBIR DATOS DEL ESCLAVO
// ========================================
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
incomingData.trim();
if (incomingData.startsWith("BOTON:")) {
// Formato: BOTON:2:D2
int botonNumero = incomingData.substring(6, 7).toInt();
String botonPin = incomingData.substring(8);
if (botonNumero >= 1 && botonNumero <= 6) {
lastReceiveTime = millis();
ultimoRecibido = botonNumero;
ultimoRemitente = botonPin;
paquetesRecibidos++;
// Actualizar LCD
actualizarLCD();
// Mostrar el número en el display 7 segmentos
mostrarNumero(botonNumero);
ultimoValorMostrado = botonNumero;
// Enviar confirmación al esclavo
Serial.print("OK:");
Serial.print(botonNumero);
Serial.print(":");
Serial.print(botonPin);
Serial.println();
delay(10);
}
}
else if (incomingData == "SISTEMA_ESCLAVO_LISTO") {
estadoComunicacion = "Esclavo OK";
actualizarLCD();
}
else if (incomingData == "ESCLAVO_HEARTBEAT") {
lastReceiveTime = millis();
estadoComunicacion = "Heartbeat OK";
actualizarLCD();
// Parpadear display de 7 segmentos
mostrarNumero(0);
delay(50);
mostrarNumero(ultimoValorMostrado > 0 ? ultimoValorMostrado : 0);
}
incomingData = "";
}
else if (c != '\r') {
incomingData += c;
}
}
// ========================================
// 2. VERIFICAR TIMEOUT DE COMUNICACIÓN
// ========================================
if (lastReceiveTime != 0 && (millis() - lastReceiveTime) > 10000) {
estadoComunicacion = "TIMEOUT!";
actualizarLCD();
mostrarError();
lastReceiveTime = millis() - 5000; // Para no mostrar error constantemente
}
// ========================================
// 3. HEARTBEAT DEL MAESTRO
// ========================================
if (millis() - lastHeartbeatTime > 30000) {
lastHeartbeatTime = millis();
Serial.print("MAESTRO:Heartbeat|Paq:");
Serial.print(paquetesRecibidos);
Serial.println();
}
// Actualizar LCD periódicamente
if (millis() - lastLcdUpdate > 500) {
lastLcdUpdate = millis();
actualizarLCD();
}
}
// ========================================
// ACTUALIZAR LCD CON ESTADO
// ========================================
void actualizarLCD() {
// Primera línea: Último valor recibido
lcd.setCursor(0, 0);
lcd.print("Recibido: ");
if (ultimoRecibido > 0) {
lcd.print(ultimoRecibido);
lcd.print(" (");
lcd.print(ultimoRemitente);
lcd.print(") ");
} else {
lcd.print("- ");
}
// Segunda línea: Estado de comunicación
lcd.setCursor(0, 1);
lcd.print("Estado: ");
lcd.print(estadoComunicacion);
lcd.print(" ");
// Mostrar contador de paquetes al final
lcd.setCursor(12, 1);
lcd.print("P:");
lcd.print(paquetesRecibidos % 100);
}
// ========================================
// MOSTRAR NÚMERO EN 7 SEGMENTOS
// ========================================
void mostrarNumero(int numero) {
if (numero < 0 || numero > 9) numero = 0;
byte patron = digitPatterns[numero];
digitalWrite(STCP_PIN, LOW);
shiftOut(DS_PIN, SHCP_PIN, LSBFIRST, patron);
digitalWrite(STCP_PIN, HIGH);
}
// ========================================
// MOSTRAR ERROR EN DISPLAY 7 SEGMENTOS
// ========================================
void mostrarError() {
byte errorPattern = 0b01111001; // 'E'
digitalWrite(STCP_PIN, LOW);
shiftOut(DS_PIN, SHCP_PIN, LSBFIRST, errorPattern);
digitalWrite(STCP_PIN, HIGH);
delay(100);
if (ultimoValorMostrado > 0) {
mostrarNumero(ultimoValorMostrado);
} else {
mostrarNumero(0);
}
}
CODIGO ESCLAVO
// ============================================
// ESCLAVO - LEE BOTONES (D2-D7) Y ENVÍA POR RS485
// MUESTRA EN DISPLAY 7 SEGMENTOS (74HC595)
// MUESTRA ESTADO EN LCD I2C
// ============================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// LCD I2C (probar 0x27 o 0x3F)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Pines del 74HC595
#define DS_PIN 11
#define SHCP_PIN 13
#define STCP_PIN 12
// Pines de botones (D2 a D7)
const int botones[] = {2, 3, 4, 5, 6, 7};
const int numBotones = 6;
const char* botonNombres[] = {"D2", "D3", "D4", "D5", "D6", "D7"};
// Variables
int ultimoEstado[6] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
int ultimoValorMostrado = -1;
unsigned long lastDebounceTime[6] = {0, 0, 0, 0, 0, 0};
unsigned long lastSendTime = 0;
unsigned long lastLcdUpdate = 0;
// Buffer para comunicación
String incomingData = "";
// Variables para LCD
int ultimoEnvio = -1;
int ultimaRecepcion = -1;
String ultimoComandoRecibido = "";
// Tabla de segmentos para números 0-9 (cátodo común)
byte digitPatterns[] = {
0b11111100, // 0
0b01100000, // 1
0b11011010, // 2
0b11110010, // 3
0b01100110, // 4
0b10110110, // 5
0b10111110, // 6
0b11100000, // 7
0b11111110, // 8
0b11110110 // 9
};
void setup() {
// Inicializar RS485
Serial.begin(19200);
// Inicializar LCD
lcd.init();
lcd.backlight();
lcd.clear();
// Configurar pines del 74HC595
pinMode(DS_PIN, OUTPUT);
pinMode(SHCP_PIN, OUTPUT);
pinMode(STCP_PIN, OUTPUT);
// Configurar pines de botones como entradas con pull-up
for (int i = 0; i < numBotones; i++) {
pinMode(botones[i], INPUT_PULLUP);
}
// Mostrar pantalla de inicio
lcd.setCursor(0, 0);
lcd.print("=== ESCLAVO ===");
lcd.setCursor(0, 1);
lcd.print("Iniciando...");
delay(2000);
lcd.clear();
// Configurar LCD con encabezados fijos
lcd.setCursor(0, 0);
lcd.print("Envio: Rec:");
lcd.setCursor(0, 1);
lcd.print("Boton: ");
// Mostrar 0 en display 7 segmentos
mostrarNumero(0);
delay(500);
mostrarNumero(8);
delay(500);
mostrarNumero(0);
// Enviar mensaje de inicio por RS485
Serial.println("SISTEMA_ESCLAVO_LISTO");
}
void loop() {
// ========================================
// 1. LEER BOTONES Y ENVIAR AL MAESTRO
// ========================================
for (int i = 0; i < numBotones; i++) {
int lectura = digitalRead(botones[i]);
if (lectura == LOW && ultimoEstado[i] == HIGH) {
if (millis() - lastDebounceTime[i] > 50) {
lastDebounceTime[i] = millis();
int numeroBoton = i + 1;
String botonNombre = botonNombres[i];
// Enviar al maestro
Serial.print("BOTON:");
Serial.print(numeroBoton);
Serial.print(":");
Serial.print(botonNombre);
Serial.println();
// Actualizar LCD con lo que se envió
ultimoEnvio = numeroBoton;
actualizarLCD();
// Mostrar en display 7 segmentos local
mostrarNumero(numeroBoton);
ultimoValorMostrado = numeroBoton;
delay(50);
}
}
ultimoEstado[i] = lectura;
}
// ========================================
// 2. RECIBIR CONFIRMACIONES DEL MAESTRO
// ========================================
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
incomingData.trim();
if (incomingData.startsWith("OK:")) {
// Formato: OK:2:D2
int numeroConfirmado = incomingData.substring(3, 4).toInt();
ultimoComandoRecibido = incomingData;
// Actualizar LCD con lo que se recibió
ultimaRecepcion = numeroConfirmado;
actualizarLCD();
// Parpadear display para confirmar
mostrarNumero(0);
delay(100);
mostrarNumero(numeroConfirmado);
}
else if (incomingData.startsWith("MAESTRO:")) {
// Heartbeat del maestro
ultimoComandoRecibido = incomingData;
actualizarLCD();
}
incomingData = "";
} else if (c != '\r') {
incomingData += c;
}
}
// ========================================
// 3. REENVÍO PERIÓDICO (Heartbeat)
// ========================================
if (millis() - lastSendTime > 30000) {
lastSendTime = millis();
Serial.println("ESCLAVO_HEARTBEAT");
}
// Actualizar LCD cada 500ms (para parpadeo de indicadores)
if (millis() - lastLcdUpdate > 500) {
lastLcdUpdate = millis();
actualizarLCD();
}
}
// ========================================
// ACTUALIZAR LCD CON ESTADO
// ========================================
void actualizarLCD() {
// Primera línea: Envío y Recepción
lcd.setCursor(0, 0);
lcd.print("Envio: ");
if (ultimoEnvio > 0) {
lcd.print(ultimoEnvio);
lcd.print(" ");
} else {
lcd.print("- ");
}
lcd.setCursor(10, 0);
lcd.print("Rec: ");
if (ultimaRecepcion > 0) {
lcd.print(ultimaRecepcion);
lcd.print(" ");
} else {
lcd.print("- ");
}
// Segunda línea: Estado de botones
lcd.setCursor(0, 1);
lcd.print("Botones: ");
// Mostrar qué botones están presionados
String botonesActivos = "";
for (int i = 0; i < numBotones; i++) {
if (digitalRead(botones[i]) == LOW) {
botonesActivos += String(i + 1);
} else {
botonesActivos += "-";
}
}
lcd.print(botonesActivos);
lcd.print(" ");
}
// ========================================
// MOSTRAR NÚMERO EN 7 SEGMENTOS
// ========================================
void mostrarNumero(int numero) {
if (numero < 0 || numero > 9) numero = 0;
byte patron = digitPatterns[numero];
digitalWrite(STCP_PIN, LOW);
shiftOut(DS_PIN, SHCP_PIN, LSBFIRST, patron);
digitalWrite(STCP_PIN, HIGH);
}
EJERCICIO 3: COMUNICACIÓN MAESTRO-ESCLAVO CON RETROALIMENTACIÓN, pero para 5 tarjetas
CODIGO MAESTRO
// ============================================
// MAESTRO RS485 - CON BOTONES PROPIOS
// RECIBE DE 4 ESCLAVOS (IDs 1-4)
// LEE SUS PROPIOS BOTONES (D2-D7)
// MUESTRA EN LCD Y 7 SEGMENTOS
// RETRANSMITE A TODOS LOS ESCLAVOS
// ============================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// LCD I2C
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Pines del 74HC595
#define DS_PIN 11
#define SHCP_PIN 13
#define STCP_PIN 12
// ID del maestro (0 para distinguirlo)
#define MASTER_ID 0
// Pines de botones del maestro (D2 a D7)
const int botones[] = {2, 3, 4, 5, 6, 7};
const int numBotones = 6;
// Configuración de esclavos
const int numEsclavos = 4;
int ultimoRecibidoPorEsclavo[5] = {0, -1, -1, -1, -1}; // Índice 1-4
unsigned long lastReceiveTime[5] = {0, 0, 0, 0, 0};
bool esclavoConectado[5] = {false, false, false, false, false};
// Variables para botones del maestro
int ultimoEstadoBotones[6] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
unsigned long lastDebounceTime[6] = {0, 0, 0, 0, 0, 0};
int ultimoEnvioMaestro = -1;
// Variables generales
int ultimoValorMostrado = -1;
unsigned long lastHeartbeatTime = 0;
unsigned long lastLcdUpdate = 0;
int ultimoBotonGlobal = -1;
int ultimoEsclavoGlobal = -1;
// Buffer para comunicación
String incomingData = "";
// Patrones de segmentos corregidos (cátodo común)
byte digitPatterns[] = {
0b11111100, // 0
0b01100000, // 1
0b11011010, // 2
0b11110010, // 3
0b01100110, // 4
0b10110110, // 5
0b10111110, // 6
0b11100000, // 7
0b11111110, // 8
0b11110110 // 9
};
void setup() {
// Inicializar RS485
Serial.begin(19200);
// Inicializar LCD
lcd.init();
lcd.backlight();
lcd.clear();
// Configurar 74HC595
pinMode(DS_PIN, OUTPUT);
pinMode(SHCP_PIN, OUTPUT);
pinMode(STCP_PIN, OUTPUT);
// Configurar botones del maestro
for (int i = 0; i < numBotones; i++) {
pinMode(botones[i], INPUT_PULLUP);
}
// Pantalla de inicio
lcd.setCursor(0, 0);
lcd.print("=== MAESTRO ===");
lcd.setCursor(0, 1);
lcd.print("Con Botones");
delay(2000);
lcd.clear();
// Configurar LCD
lcd.setCursor(0, 0);
lcd.print("Ultimo: E B");
lcd.setCursor(0, 1);
lcd.print("E0:? E1:? E2:? E3:?");
// Mostrar 0 en display
mostrarNumero(0);
delay(500);
mostrarNumero(8);
delay(500);
mostrarNumero(0);
// Anunciar inicio
Serial.println("MASTER_READY");
}
void loop() {
// ========================================
// 1. LEER BOTONES DEL PROPIO MAESTRO
// ========================================
for (int i = 0; i < numBotones; i++) {
int lectura = digitalRead(botones[i]);
if (lectura == LOW && ultimoEstadoBotones[i] == HIGH) {
if (millis() - lastDebounceTime[i] > 50) {
lastDebounceTime[i] = millis();
int numeroBoton = i + 1;
// Actualizar variable local
ultimoEnvioMaestro = numeroBoton;
ultimoBotonGlobal = numeroBoton;
ultimoEsclavoGlobal = MASTER_ID;
// Mostrar en display 7 segmentos del maestro
mostrarNumero(numeroBoton);
ultimoValorMostrado = numeroBoton;
// Actualizar LCD del maestro
actualizarLCD();
// ========================================
// RETRANSMITIR A TODOS LOS ESCLAVOS
// El maestro también usa el mismo formato
// ========================================
Serial.print("FROM_MASTER:");
Serial.print(MASTER_ID);
Serial.print(":BOTON:");
Serial.println(numeroBoton);
delay(50);
}
}
ultimoEstadoBotones[i] = lectura;
}
// ========================================
// 2. RECIBIR DATOS DE LOS ESCLAVOS
// ========================================
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
incomingData.trim();
// Formato esperado: "ID:BOTON:NUMERO" (ej: "2:BOTON:3")
if (incomingData.indexOf(":BOTON:") > 0 && !incomingData.startsWith("FROM_MASTER")) {
int primerPunto = incomingData.indexOf(':');
int esclavoID = incomingData.substring(0, primerPunto).toInt();
int inicioBoton = primerPunto + 7; // después de ":BOTON:"
int botonNumero = incomingData.substring(inicioBoton).toInt();
if (esclavoID >= 1 && esclavoID <= 4 && botonNumero >= 1 && botonNumero <= 6) {
// Actualizar estado
lastReceiveTime[esclavoID] = millis();
esclavoConectado[esclavoID] = true;
ultimoRecibidoPorEsclavo[esclavoID] = botonNumero;
ultimoBotonGlobal = botonNumero;
ultimoEsclavoGlobal = esclavoID;
// Actualizar LCD del maestro
actualizarLCD();
// Mostrar en display 7 segmentos del maestro
mostrarNumero(botonNumero);
ultimoValorMostrado = botonNumero;
// ========================================
// RETRANSMITIR A TODOS LOS ESCLAVOS
// ========================================
Serial.print("FROM_MASTER:");
Serial.print(esclavoID);
Serial.print(":BOTON:");
Serial.println(botonNumero);
delay(10);
}
}
else if (incomingData.startsWith("FROM_MASTER:")) {
// Esto es un eco de lo que el propio maestro envió
// No hacer nada para evitar bucles
}
else if (incomingData.startsWith("SLAVE_READY:")) {
int esclavoID = incomingData.substring(12).toInt();
esclavoConectado[esclavoID] = true;
lastReceiveTime[esclavoID] = millis();
actualizarLCD();
}
else if (incomingData.startsWith("HEARTBEAT_SLAVE:")) {
int esclavoID = incomingData.substring(17).toInt();
lastReceiveTime[esclavoID] = millis();
esclavoConectado[esclavoID] = true;
actualizarLCD();
}
incomingData = "";
}
else if (c != '\r') {
incomingData += c;
}
}
// ========================================
// 3. VERIFICAR TIMEOUT DE ESCLAVOS
// ========================================
for (int i = 1; i <= numEsclavos; i++) {
if (esclavoConectado[i] && (millis() - lastReceiveTime[i]) > 15000) {
esclavoConectado[i] = false;
ultimoRecibidoPorEsclavo[i] = -1;
actualizarLCD();
}
}
// ========================================
// 4. HEARTBEAT DEL MAESTRO (cada 30 seg)
// ========================================
if (millis() - lastHeartbeatTime > 30000) {
lastHeartbeatTime = millis();
Serial.println("HEARTBEAT_MASTER");
}
// Actualizar LCD periódicamente
if (millis() - lastLcdUpdate > 500) {
lastLcdUpdate = millis();
actualizarLCD();
}
}
// ========================================
// ACTUALIZAR LCD DEL MAESTRO
// ========================================
void actualizarLCD() {
// Primera línea: Último botón recibido (de cualquier origen)
lcd.setCursor(0, 0);
lcd.print("Ultimo: E");
if (ultimoEsclavoGlobal >= 0) {
lcd.print(ultimoEsclavoGlobal);
} else {
lcd.print("-");
}
lcd.print(" B");
if (ultimoBotonGlobal > 0) {
lcd.print(ultimoBotonGlobal);
lcd.print(" ");
} else {
lcd.print("- ");
}
// Segunda línea: Estado de maestro (E0) y esclavos (E1-E3)
lcd.setCursor(0, 1);
// Maestro (E0) - mostrar último botón que envió
lcd.print("E0:");
if (ultimoEnvioMaestro > 0) {
lcd.print(ultimoEnvioMaestro);
} else {
lcd.print("?");
}
// Esclavo 1
lcd.print(" E1:");
if (esclavoConectado[1] && ultimoRecibidoPorEsclavo[1] > 0) {
lcd.print(ultimoRecibidoPorEsclavo[1]);
} else if (esclavoConectado[1]) {
lcd.print("?");
} else {
lcd.print("X");
}
// Esclavo 2
lcd.print(" E2:");
if (esclavoConectado[2] && ultimoRecibidoPorEsclavo[2] > 0) {
lcd.print(ultimoRecibidoPorEsclavo[2]);
} else if (esclavoConectado[2]) {
lcd.print("?");
} else {
lcd.print("X");
}
// Esclavo 3
lcd.print(" E3:");
if (esclavoConectado[3] && ultimoRecibidoPorEsclavo[3] > 0) {
lcd.print(ultimoRecibidoPorEsclavo[3]);
} else if (esclavoConectado[3]) {
lcd.print("?");
} else {
lcd.print("X");
}
lcd.print(" ");
}
// ========================================
// MOSTRAR NÚMERO EN 7 SEGMENTOS
// ========================================
void mostrarNumero(int numero) {
if (numero < 0 || numero > 9) numero = 0;
byte patron = digitPatterns[numero];
digitalWrite(STCP_PIN, LOW);
shiftOut(DS_PIN, SHCP_PIN, LSBFIRST, patron);
digitalWrite(STCP_PIN, HIGH);
}
CODIGO ESCLAVO
// ============================================
// ESCLAVO RS485 CON DIRECCIÓN
// ID: 1, 2, 3 o 4 (CAMBIAR SEGÚN CORRESPONDA)
// LEE BOTONES D2-D7, ENVÍA AL MAESTRO
// RECIBE Y MUESTRA RETRANSMISIONES DEL MAESTRO
// ============================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ========== CONFIGURACIÓN ÚNICA POR ESCLAVO ==========
// CAMBIAR ESTE VALOR PARA CADA ESCLAVO: 1, 2, 3 o 4
#define MI_ID 1 // <--- CAMBIAR A 1, 2, 3 o 4
// =====================================================
// LCD I2C (probar 0x27 o 0x3F)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Pines del 74HC595
#define DS_PIN 11
#define SHCP_PIN 13
#define STCP_PIN 12
// Pines de botones (D2 a D7)
const int botones[] = {2, 3, 4, 5, 6, 7};
const int numBotones = 6;
// Variables
int ultimoEstado[6] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
int ultimoValorMostrado = -1;
unsigned long lastDebounceTime[6] = {0, 0, 0, 0, 0, 0};
unsigned long lastHeartbeatTime = 0;
// Buffer para comunicación
String incomingData = "";
// Variables para LCD
int ultimoEnvio = -1;
int ultimaRecepcion = -1;
int ultimoEsclavoOrigen = -1;
// Patrones de segmentos corregidos (cátodo común)
byte digitPatterns[] = {
0b11111100, // 0
0b01100000, // 1
0b11011010, // 2
0b11110010, // 3
0b01100110, // 4
0b10110110, // 5
0b10111110, // 6
0b11100000, // 7
0b11111110, // 8
0b11110110 // 9
};
void setup() {
// Inicializar RS485
Serial.begin(19200);
// Inicializar LCD
lcd.init();
lcd.backlight();
lcd.clear();
// Configurar pines del 74HC595
pinMode(DS_PIN, OUTPUT);
pinMode(SHCP_PIN, OUTPUT);
pinMode(STCP_PIN, OUTPUT);
// Configurar botones
for (int i = 0; i < numBotones; i++) {
pinMode(botones[i], INPUT_PULLUP);
}
// Pantalla de inicio
lcd.setCursor(0, 0);
lcd.print("=== ESCLAVO ");
lcd.print(MI_ID);
lcd.print(" ===");
lcd.setCursor(0, 1);
lcd.print("Iniciando...");
delay(2000);
lcd.clear();
// Configurar LCD
lcd.setCursor(0, 0);
lcd.print("ID:");
lcd.print(MI_ID);
lcd.print(" Envio: Rec:");
lcd.setCursor(0, 1);
lcd.print("Ultimo: E B");
// Mostrar ID en display 7 segmentos
mostrarNumero(MI_ID);
delay(1000);
mostrarNumero(0);
// Anunciar presencia en el bus
Serial.print("SLAVE_READY:");
Serial.println(MI_ID);
}
void loop() {
// ========================================
// 1. LEER BOTONES Y ENVIAR AL MAESTRO
// ========================================
for (int i = 0; i < numBotones; i++) {
int lectura = digitalRead(botones[i]);
if (lectura == LOW && ultimoEstado[i] == HIGH) {
if (millis() - lastDebounceTime[i] > 50) {
lastDebounceTime[i] = millis();
int numeroBoton = i + 1;
// Enviar al maestro con formato: ID:BOTON:NUMERO
Serial.print(MI_ID);
Serial.print(":BOTON:");
Serial.println(numeroBoton);
// Actualizar LCD con lo que se envió
ultimoEnvio = numeroBoton;
actualizarLCD();
// Mostrar en display 7 segmentos local (lo que presionó)
mostrarNumero(numeroBoton);
ultimoValorMostrado = numeroBoton;
delay(50);
}
}
ultimoEstado[i] = lectura;
}
// ========================================
// 2. RECIBIR DATOS DEL MAESTRO
// ========================================
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
incomingData.trim();
// ========================================
// PROCESAR MENSAJES DEL MAESTRO
// ========================================
// Formato esperado: "FROM_MASTER:ID:BOTON:NUMERO"
// Ejemplo: "FROM_MASTER:2:BOTON:5"
if (incomingData.startsWith("FROM_MASTER:")) {
// Extraer todo después de "FROM_MASTER:"
String resto = incomingData.substring(12);
// Buscar el primer ":" después del ID del esclavo
int primerPunto = resto.indexOf(':');
int esclavoOrigen = resto.substring(0, primerPunto).toInt();
// Buscar "BOTON:" y extraer el número
int posBoton = resto.indexOf("BOTON:");
int numeroRecibido = resto.substring(posBoton + 6).toInt();
// ========================================
// MOSTRAR EN LCD DEL ESCLAVO
// ========================================
ultimaRecepcion = numeroRecibido;
ultimoEsclavoOrigen = esclavoOrigen;
actualizarLCD();
// ========================================
// MOSTRAR EN DISPLAY 7 SEGMENTOS
// ========================================
// ¡El número que se muestra en todos los esclavos!
mostrarNumero(numeroRecibido);
ultimoValorMostrado = numeroRecibido;
// Pequeño parpadeo para indicar recepción
delay(50);
}
else if (incomingData.startsWith("HEARTBEAT_MASTER")) {
// Heartbeat del maestro - solo actualizar LCD
actualizarLCD();
}
else if (incomingData.startsWith("MASTER_READY")) {
// Maestro listo
lcd.setCursor(0, 1);
lcd.print("Maestro OK ");
delay(500);
actualizarLCD();
}
incomingData = "";
} else if (c != '\r') {
incomingData += c;
}
}
// ========================================
// 3. HEARTBEAT DEL ESCLAVO (cada 30 seg)
// ========================================
if (millis() - lastHeartbeatTime > 30000) {
lastHeartbeatTime = millis();
Serial.print("HEARTBEAT_SLAVE:");
Serial.println(MI_ID);
}
// Actualizar LCD periódicamente
static unsigned long lastLcdUpdate = 0;
if (millis() - lastLcdUpdate > 500) {
lastLcdUpdate = millis();
actualizarLCD();
}
}
// ========================================
// ACTUALIZAR LCD DEL ESCLAVO
// ========================================
void actualizarLCD() {
// Primera línea: ID, Envío local, Recepción global
lcd.setCursor(0, 0);
lcd.print("ID:");
lcd.print(MI_ID);
lcd.print(" Env:");
if (ultimoEnvio > 0) {
lcd.print(ultimoEnvio);
lcd.print(" ");
} else {
lcd.print("- ");
}
lcd.print(" Rec:");
if (ultimaRecepcion > 0) {
lcd.print(ultimaRecepcion);
lcd.print(" ");
} else {
lcd.print("- ");
}
// Segunda línea: Último botón recibido de qué esclavo
lcd.setCursor(0, 1);
lcd.print("Ultimo: E");
if (ultimoEsclavoOrigen > 0) {
lcd.print(ultimoEsclavoOrigen);
} else {
lcd.print("-");
}
lcd.print(" B");
if (ultimaRecepcion > 0) {
lcd.print(ultimaRecepcion);
lcd.print(" ");
} else {
lcd.print("- ");
}
// Mostrar estado de botones locales (al final)
lcd.setCursor(10, 1);
for (int i = 0; i < numBotones; i++) {
if (digitalRead(botones[i]) == LOW) {
lcd.print(i + 1);
} else {
lcd.print("-");
}
}
lcd.print(" ");
}
// ========================================
// MOSTRAR NÚMERO EN 7 SEGMENTOS
// ========================================
void mostrarNumero(int numero) {
if (numero < 0 || numero > 9) numero = 0;
byte patron = digitPatterns[numero];
digitalWrite(STCP_PIN, LOW);
shiftOut(DS_PIN, SHCP_PIN, LSBFIRST, patron);
digitalWrite(STCP_PIN, HIGH);
}
EJERCICIO 4: Comunicación bidireccional completa (P2P) (TODO MAESTRO)
CODIGO MAESTRO – ESCLAVO (mismo esclavo)
// ============================================
// SISTEMA P2P - COMUNICACIÓN BIDIRECCIONAL COMPLETA
// TODOS los dispositivos usan el MISMO código
// Solo cambiar MI_ID (0 para maestro, 1-4 para esclavos)
// ============================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// ========== CONFIGURACIÓN ÚNICA ==========
// CAMBIAR ESTE VALOR EN CADA DISPOSITIVO:
// 0 = Maestro (o nodo central)
// 1, 2, 3, 4 = Esclavos
#define MI_ID 4 // <--- CAMBIAR SEGÚN DISPOSITIVO
// IDs especiales
#define ID_BROADCAST 255 // Enviar a todos
#define ID_MAESTRO 0 // ID del nodo maestro
// ==========================================
LiquidCrystal_I2C lcd(0x27, 16, 2);
#define DS_PIN 11
#define SHCP_PIN 13
#define STCP_PIN 12
// Botones (D2-D7)
const int botones[] = {2, 3, 4, 5, 6, 7};
const int numBotones = 6;
// Variables de botones
int ultimoEstado[6] = {HIGH, HIGH, HIGH, HIGH, HIGH, HIGH};
int ultimoValorMostrado = -1;
unsigned long lastDebounceTime[6] = {0, 0, 0, 0, 0, 0};
unsigned long lastHeartbeatTime = 0;
// Buffer para comunicación
String incomingData = "";
// Variables para LCD
int ultimoEnvio = -1;
int ultimoDestino = -1;
int ultimaRecepcion = -1;
int ultimoOrigen = -1;
// Variables para control de colisiones (CSMA/CD simple)
unsigned long lastTransmissionTime = 0;
const unsigned long MIN_GAP_BETWEEN_MSG = 50; // 50ms entre mensajes
bool waitingForClearBus = false;
unsigned long waitStartTime = 0;
// Patrones de segmentos
byte digitPatterns[] = {
0b11111100, // 0
0b01100000, // 1
0b11011010, // 2
0b11110010, // 3
0b01100110, // 4
0b10110110, // 5
0b10111110, // 6
0b11100000, // 7
0b11111110, // 8
0b11110110 // 9
};
// ========================================
// ENVIAR MENSAJE A OTRO DISPOSITIVO
// ========================================
bool enviarMensaje(int destino, String tipo, String dato) {
// Verificar tiempo mínimo entre mensajes
if (millis() - lastTransmissionTime < MIN_GAP_BETWEEN_MSG) {
waitingForClearBus = true;
waitStartTime = millis();
return false;
}
String mensaje = "DE:" + String(destino) + ":" + String(MI_ID) + ":" + tipo + ":" + dato;
Serial.println(mensaje);
lastTransmissionTime = millis();
waitingForClearBus = false;
// Actualizar LCD con lo que se envió
ultimoEnvio = dato.toInt();
ultimoDestino = destino;
actualizarLCD();
return true;
}
// ========================================
// PROCESAR MENSAJE RECIBIDO
// ========================================
void procesarMensaje(String mensaje) {
// Formato: "DE:DESTINO:ORIGEN:TIPO:DATO"
if (!mensaje.startsWith("DE:")) return;
String resto = mensaje.substring(3); // Quitar "DE:"
int primerPunto = resto.indexOf(':');
int destino = resto.substring(0, primerPunto).toInt();
String resto2 = resto.substring(primerPunto + 1);
int segundoPunto = resto2.indexOf(':');
int origen = resto2.substring(0, segundoPunto).toInt();
String resto3 = resto2.substring(segundoPunto + 1);
int tercerPunto = resto3.indexOf(':');
String tipo = resto3.substring(0, tercerPunto);
String dato = resto3.substring(tercerPunto + 1);
// Verificar si el mensaje es para mí o es broadcast
if (destino == MI_ID || destino == ID_BROADCAST) {
// Actualizar LCD con lo que se recibió
ultimaRecepcion = dato.toInt();
ultimoOrigen = origen;
actualizarLCD();
// Mostrar en display 7 segmentos
int numero = dato.toInt();
if (numero >= 0 && numero <= 9) {
mostrarNumero(numero);
ultimoValorMostrado = numero;
}
// Mostrar en LCD el mensaje recibido (parpadeo rápido)
lcd.setCursor(12, 1);
lcd.print("RX");
delay(100);
lcd.setCursor(12, 1);
lcd.print(" ");
// Si es un comando especial, procesarlo
if (tipo == "CMD") {
procesarComando(origen, dato);
}
}
}
// ========================================
// PROCESAR COMANDOS ESPECIALES
// ========================================
void procesarComando(int origen, String comando) {
if (comando == "HEARTBEAT") {
// Responder al heartbeat
if (MI_ID != ID_MAESTRO) {
enviarMensaje(origen, "RSP", "ALIVE");
}
}
else if (comando == "BLINK") {
// Parpadear display 7 segmentos
for (int i = 0; i < 3; i++) {
mostrarNumero(8);
delay(200);
mostrarNumero(ultimoValorMostrado > 0 ? ultimoValorMostrado : 0);
delay(200);
}
}
else if (comando.startsWith("SHOW:")) {
int numero = comando.substring(5).toInt();
mostrarNumero(numero);
ultimoValorMostrado = numero;
}
}
// ========================================
// ESCANEAR BUS Y RETRANSMITIR (OPCIONAL)
// ========================================
void escanearBus() {
// El maestro puede hacer ping a todos los dispositivos
static unsigned long lastScan = 0;
if (MI_ID == ID_MAESTRO && (millis() - lastScan) > 60000) {
lastScan = millis();
enviarMensaje(ID_BROADCAST, "CMD", "HEARTBEAT");
}
}
// ========================================
// SETUP
// ========================================
void setup() {
Serial.begin(19200);
lcd.init();
lcd.backlight();
lcd.clear();
pinMode(DS_PIN, OUTPUT);
pinMode(SHCP_PIN, OUTPUT);
pinMode(STCP_PIN, OUTPUT);
for (int i = 0; i < numBotones; i++) {
pinMode(botones[i], INPUT_PULLUP);
}
// Pantalla de inicio
lcd.setCursor(0, 0);
lcd.print("=== P2P SYS ===");
lcd.setCursor(0, 1);
lcd.print("ID:");
lcd.print(MI_ID);
lcd.print(" Listo");
delay(2000);
lcd.clear();
// Configurar LCD
lcd.setCursor(0, 0);
lcd.print("ID:");
lcd.print(MI_ID);
lcd.print(" Envio:> Rec:<");
lcd.setCursor(0, 1);
lcd.print("Ultimo: E B");
mostrarNumero(MI_ID);
delay(1000);
mostrarNumero(0);
// Anunciar presencia en el bus (broadcast)
delay(100); // Pequeña pausa para evitar colisiones iniciales
enviarMensaje(ID_BROADCAST, "STATUS", "ONLINE");
}
// ========================================
// LOOP PRINCIPAL
// ========================================
void loop() {
// ========================================
// 1. LEER BOTONES Y ENVIAR
// ========================================
for (int i = 0; i < numBotones; i++) {
int lectura = digitalRead(botones[i]);
if (lectura == LOW && ultimoEstado[i] == HIGH) {
if (millis() - lastDebounceTime[i] > 50) {
lastDebounceTime[i] = millis();
int numeroBoton = i + 1;
// En P2P, el usuario elige el destino (por ahora, broadcast)
// Podrías agregar un selector de destino con botones extra
enviarMensaje(ID_BROADCAST, "BOTON", String(numeroBoton));
// También mostrar localmente
mostrarNumero(numeroBoton);
ultimoValorMostrado = numeroBoton;
delay(50);
}
}
ultimoEstado[i] = lectura;
}
// ========================================
// 2. RECIBIR MENSAJES
// ========================================
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
incomingData.trim();
if (incomingData.length() > 0) {
procesarMensaje(incomingData);
}
incomingData = "";
} else if (c != '\r') {
incomingData += c;
}
}
// ========================================
// 3. ESCANEAR BUS (solo maestro)
// ========================================
escanearBus();
// ========================================
// 4. HEARTBEAT
// ========================================
if (millis() - lastHeartbeatTime > 30000) {
lastHeartbeatTime = millis();
// Anunciar que sigo vivo
enviarMensaje(ID_BROADCAST, "STATUS", "ALIVE");
}
// ========================================
// 5. ACTUALIZAR LCD
// ========================================
static unsigned long lastLcdUpdate = 0;
if (millis() - lastLcdUpdate > 500) {
lastLcdUpdate = millis();
actualizarLCD();
}
}
// ========================================
// ACTUALIZAR LCD
// ========================================
void actualizarLCD() {
// Primera línea: ID y envío
lcd.setCursor(0, 0);
lcd.print("ID:");
lcd.print(MI_ID);
lcd.print(" Env:");
if (ultimoEnvio > 0) {
lcd.print(ultimoEnvio);
lcd.print(">");
} else {
lcd.print("->");
}
if (ultimoDestino >= 0) {
if (ultimoDestino == ID_BROADCAST) {
lcd.print("T");
} else {
lcd.print(ultimoDestino);
}
} else {
lcd.print("-");
}
lcd.print(" Rec:<");
if (ultimaRecepcion > 0) {
lcd.print(ultimaRecepcion);
} else {
lcd.print("-");
}
// Segunda línea: origen del último mensaje
lcd.setCursor(0, 1);
lcd.print("Ultimo: E");
if (ultimoOrigen >= 0) {
lcd.print(ultimoOrigen);
} else {
lcd.print("-");
}
lcd.print(" B");
if (ultimaRecepcion > 0) {
lcd.print(ultimaRecepcion);
lcd.print(" ");
} else {
lcd.print("- ");
}
// Estado de botones locales
lcd.setCursor(10, 1);
for (int i = 0; i < numBotones; i++) {
if (digitalRead(botones[i]) == LOW) {
lcd.print(i + 1);
} else {
lcd.print("-");
}
}
lcd.print(" ");
// Indicador de bus ocupado
if (waitingForClearBus) {
lcd.setCursor(15, 0);
lcd.print("B");
} else {
lcd.setCursor(15, 0);
lcd.print(" ");
}
}
// ========================================
// MOSTRAR NÚMERO EN 7 SEGMENTOS
// ========================================
void mostrarNumero(int numero) {
if (numero < 0 || numero > 9) numero = 0;
byte patron = digitPatterns[numero];
digitalWrite(STCP_PIN, LOW);
shiftOut(DS_PIN, SHCP_PIN, LSBFIRST, patron);
digitalWrite(STCP_PIN, HIGH);
}


