Sistema de Evitación de Obstáculos con IA y Arduino: Aprendizaje por Refuerzo Q-Learning
Componentes y Suministros
| Componente | Cantidad | Notas |
|---|---|---|
| Arduino Pro Mini 328 5V/16MHz (SparkFun) | 1 | |
| Sensor Ultrasónico HC-SR04 | 1 | |
| Driver Dual H-Bridge L298 (SparkFun) | 1 | |
| Motor DC 60 RPM | 2 | |
| Ruedas | 4 | |
| Cables jumper | 1 | |
| Protoboard soldable Mini (SparkFun) | 1 | |
| Batería Li-Ion 7.2V Recargable | 1 | Para motores |
| Batería 9V + Clip | 1 | Para Arduino |
| PCB Personalizada PCBWay | 1 | |
| Interruptor JS Series (C&K) | 1 |
Herramientas Necesarias
- Soldador + estaño sin plomo + flux + bomba desoldar
- Pelacables/cortador 18-10 AWG
- Pistola silicona caliente
- Cinta doble cara
Aplicaciones y Servicios
- Arduino IDE
Descripción del Proyecto
Robot Evita-Obstáculos con IA / Aprendizaje por Refuerzo
Objetivo: Aprender a evitar obstáculos en "N" episodios y descubrir la acción óptima (girar a la derecha).
Algoritmo: Q-Learning (aprendizaje por refuerzo sin modelo, off-policy).
Funcionamiento del Driver L298N
El L298N permite controlar dirección y velocidad de dos motores DC mediante señales PWM en pines ENA/ENB y lógica en IN1-IN4.
Medición de Distancia HC-SR04
Emite pulso ultrasónico (trigger 10µs), mide tiempo de eco. Distancia = (tiempo/2) / 29.1 → cm.
Conceptos Clave del Aprendizaje por Refuerzo
- ESTADO (State): Situación del robot. Aquí: 10 estados (para ilustrar entorno complejo), aunque realmente son 2: "sin obstáculo cercano" y "obstáculo delante" (<15cm).
- ACCIÓN (Action): En estado "obstáculo": ADELANTE (0), ATRÁS (1), PARAR (2), IZQUIERDA (3). En estado "libre": solo ADELANTE.
- SIGUIENTE ESTADO (Next State): Resultado tras acción: "CHOQUE" o "SUPERVIVENCIA" (vuelve a estado inicial).
- TABLA Q (Q-Table): Matriz
Q[ESTADOS][ACCIONES]inicializada a 0.0. Valores negativos (-1.0) = acción prohibida en ese estado. - ESTADO TERMINAL: No existe; el robot aprende indefinidamente.
- MATRIZ DE RECOMPENSAS: Define calidad de cada acción en cada estado. Ejemplo: girar derecha +10, adelante -10, parar -1, atrás -2.
- ENTORNO: Mundo donde opera el robot.
Hiperparámetros
- Tasa de Aprendizaje (ALPHA = 0.1): Peso de nueva info vs. antigua. 0.1-0.2 típico.
- Factor Descuento (GAMMA = 0.5): Importancia de recompensas futuras. 0.9 común; aquí 0.5 por entorno determinista simple.
- Tasa Exploración (EPSILON = 0.9): Probabilidad de acción aleatoria vs. explotar tabla Q. Decae con
EPSILON *= 0.98por episodio; se resetea a 0.9 si baja de 0.5 para exploración continua.
Algoritmo Q-Learning
- Inicializar tabla Q(s,a) = 0.
- Observar estado actual s.
- Elegir acción a con política ε-greedy: con prob. ε → aleatoria; si no →
argmax(Q[s]). - Ejecutar a, observar recompensa r y nuevo estado s'.
- Actualizar:
Q(s,a) ← (1-α)Q(s,a) + α[r + γ·max(Q(s',·))](ecuación Bellman). - s ← s' y repetir.
// Política ε-greedy
PROB = RANDOM(EPSILON);
if (PROB <= EPSILON) { ACTION = random(0,4); FLAG = 2; } // Explorar
else { ACTION = ARGMAX(Q, STATE); FLAG = 2; } // Explotar
// Actualización Q-Learning (Bellman)
Q_OLD = Q_TABLE[S][A];
Q_MAX = MAX(Q_TABLE, NEXT_S);
Q_NEW = (1-ALPHA)*Q_OLD + ALPHA*(R + GAMMA*Q_MAX);
Q_TABLE[S][A] = Q_NEW;
Código Completo (C/C++ para Arduino)
/* Robot Evita-Obstáculos con Q-Learning
Autor: Varun Walimbe
Hardware: Arduino Pro Mini, HC-SR04, L298N, 2x Motor DC 60RPM
*/
////////// PARÁMETROS HARDWARE //////////
int TRIG_PIN = 7, ECHO_PIN = 8;
int duration; float distance;
int M1 = 13, M2 = 12, M3 = 11, M4 = 10;
bool Obstacle = false; int FLAG;
////////// PARÁMETROS Q-LEARNING //////////
float ALPHA = 0.1; // Learning rate
float GAMMA = 0.5; // Discount factor
float EPSILON = 0.90; // Exploration
int REWARD; int EPISODES = 100;
int STATE = 0; int ACTION = 0;
float PROB; bool ACTION_TAKEN = false;
int NEXT_STATE;
const int STATES = 10;
int ACTIONS[4] = {1,2,3,4};
const int NUMBER_OF_ACTIONS = 4;
// Tabla Q (diario del robot)
float Q[STATES][NUMBER_OF_ACTIONS] = {
{0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0},
{0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0},
{0.0,0.0,0.0,0.0}, {0.0,0.0,0.0,0.0}
};
// Matriz de recompensas: {ADELANTE, ATRÁS, PARAR, IZQUIERDA}
int REWARDS[STATES][NUMBER_OF_ACTIONS] = {
{-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10},
{-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10}, {-10,-2,-1,10},
{-10,-2,-1,10}, {-10,-2,-1,10}
};
////////// VARIABLES ACTUALIZACIÓN Q //////////
float Q_OLD, Q_NEW, Q_MAX;
void setup() {
Serial.begin(9600);
pinMode(TRIG_PIN, OUTPUT); pinMode(ECHO_PIN, INPUT);
pinMode(M1, OUTPUT); pinMode(M2, OUTPUT); pinMode(M3, OUTPUT); pinMode(M4, OUTPUT);
randomSeed(analogRead(A0));
STATE = 0;
Serial.println("ENTRENAMIENTO INICIA EN 5 SEGUNDOS:");
delay(5000);
}
//////// FUNCIONES MOVIMIENTO ROBOT ////////
void Forward() { digitalWrite(M1,LOW); digitalWrite(M2,HIGH); digitalWrite(M3,LOW); digitalWrite(M4,HIGH); }
void Backward() { digitalWrite(M1,HIGH); digitalWrite(M2,LOW); digitalWrite(M3,HIGH); digitalWrite(M4,LOW); }
void Left() { digitalWrite(M1,HIGH); digitalWrite(M2,LOW); digitalWrite(M3,LOW); digitalWrite(M4,HIGH); }
void Right() { digitalWrite(M1,LOW); digitalWrite(M2,HIGH); digitalWrite(M3,HIGH); digitalWrite(M4,LOW); }
void Stop() { digitalWrite(M1,LOW); digitalWrite(M2,LOW); digitalWrite(M3,LOW); digitalWrite(M4,LOW); }
bool Obstacle_Avoider() {
digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIG_PIN, LOW);
duration = pulseIn(ECHO_PIN, HIGH);
distance = (duration/2)/29.1;
Obstacle = (distance < 15);
delay(10);
return Obstacle;
}
//////// FUNCIONES Q-LEARNING ////////
float RANDOM(float eps) { return random(0,100)/100.0; }
float DECAY(float p) { return p * 0.98; }
int GET_STATE() { return random(0,10); }
float MAX(float Q_Table[][4], int next_s) {
float maxVal = Q_Table[next_s][0];
for(int b=1; b<4; b++) if(Q_Table[next_s][b] > maxVal) maxVal = Q_Table[next_s][b];
return maxVal;
}
int ARGMAX(float Q_Table[][4], int s) {
float maxVal = Q_Table[s][0]; int maxIdx = 0;
for(int u=1; u<4; u++) if(Q_Table[s][u] > maxVal) { maxVal = Q_Table[s][u]; maxIdx = u; }
return maxIdx;
}
void Update(float Q_TABLE[][4], int s, int next_s, int a, int R, float lr, float df) {
Q_OLD = Q_TABLE[s][a];
Q_MAX = MAX(Q_TABLE, next_s);
Q_NEW = (1-lr)*Q_OLD + lr*(R + df*Q_MAX);
Serial.print("Q VALUE: "); Serial.println(Q_NEW);
Q_TABLE[s][a] = Q_NEW;
}
//////// BUCLE PRINCIPAL ////////
void loop() {
//////// ENTRENAMIENTO ////////
for(int I=0; I= 10) NEXT_STATE = 0;
if(NEXT_STATE < 0) NEXT_STATE = 0;
Serial.print("STATE: "); Serial.println(STATE);
FLAG = 1; break;
}
}
// Elegir acción (ε-greedy)
if(FLAG == 1) {
PROB = RANDOM(EPSILON);
if(PROB <= EPSILON) { ACTION = random(0,4); FLAG = 2; }
else { ACTION = ARGMAX(Q, STATE); FLAG = 2; }
}
// Ejecutar acción y obtener recompensa
if(FLAG == 2) {
switch(ACTION) {
case 0: Forward(); delay(1500); Stop(); REWARD = REWARDS[STATE][ACTION]; break;
case 1: Backward(); delay(2500); Stop(); REWARD = REWARDS[STATE][ACTION]; break;
case 2: Stop(); REWARD = REWARDS[STATE][ACTION]; break;
case 3: Left(); delay(2000); Stop(); REWARD = REWARDS[STATE][ACTION]; break;
}
ACTION_TAKEN = true; delay(500);
}
// Actualizar Q y preparar siguiente episodio
if(ACTION_TAKEN) {
Update(Q, STATE, NEXT_STATE, ACTION, ACTIONS, REWARD, ALPHA, GAMMA);
STATE = NEXT_STATE;
EPSILON = DECAY(EPSILON);
if(EPSILON < 0.5) EPSILON = 0.9; // Reset exploración
Serial.print("EPISODIO TERMINADO: "); Serial.println(I);
delay(7000);
}
}
//////// EVALUACIÓN (ver tabla Q) ////////
for(int y=0; y<=9; y++) {
Serial.println("SET DE VALORES Q:");
for(int l=0; l<=3; l++) { Serial.print("Q: "); Serial.println(Q[y][l]); delay(2000); }
delay(2000);
}
Serial.println("EVALUACIÓN FINALIZADA");
//////// TESTING (uso real) ////////
while(true) {
Forward(); Obstacle = Obstacle_Avoider();
if(Obstacle) {
STATE = GET_STATE();
ACTION = ARGMAX(Q, STATE);
Serial.print("ACCIÓN ELEGIDA: "); Serial.println(ACTION);
switch(ACTION) {
case 0: Forward(); delay(1500); Stop(); break;
case 1: Backward(); delay(1500); Stop(); break;
case 2: Stop(); break;
case 3: Left(); delay(2000); Stop(); break;
}
}
}
}
Esquemático
Archivo Fritzing: obstacle_avoiding_robot_using_ai_DcoMCWIOFm.fzz
Notas Importantes
- El código usa 10 estados para demostrar escalabilidad; en la práctica 2 estados son suficientes.
- La recompensa +10 para "IZQUIERDA" hace que el robot aprenda a girar izquierda (no derecha como menciona el texto original; ajustar matriz REWARDS si se desea otro comportamiento).
- EPSILON decae pero se resetea para exploración vitalicia (no hay estado terminal).
- Corriente de motores y alimentación: separar lógica (9V) y potencia (7.2V Li-Ion).
Proceso de manufactura
- Robot móvil que evita obstáculos basado en visión en Raspberry Pi
- Bosch presenta solución de fabricación impulsada por IA en Hannover Messe
- Robótica Impulsada por IA: Máquinas Inteligentes de Vanguardia
- IA predice ventaja cuántica: red neuronal pronostica comportamiento del sistema
- La IA es Más que una App: Es una Metodología Poderosa para el Éxito Empresarial
- Arrow Electronics lanza AI Experience Tour con Intel, Microsoft, Nvidia
- Inteligencia artificial en retail: beneficios probados, no una moda pasajera
- Kubernetes Impulsa Startups de IA con Soluciones Contenedorizadas Escalables
- Cómo la IA está transformando el panorama del Internet de las Cosas
- La inteligencia artificial aún no está lista para ciberseguridad de sistemas de control industrial
- Seguimiento de la deforestación en el Amazonas impulsado por IA