ROBOT SIGUE LINEAS - SENSOR QTR8A - MOTORES SOBRE PCB
DISEÑO FRITZING
*Diseñado para sensor QTR8a.
*Puede usar motores Amarillos de 220 RPM o motores N20, N30.
*Motores directamente sobre el PCB, requiere porta motores metálicos. El porta sensor es en impresión 3D y se atornilla al PCB.
CODIGO ARDUINO
//ROBOT SCRAWLER - BATERIA 2S
//SIN CONTROL ADAPTATIVO DE VELOCIDAD
//TIEMPO: 11:27
#include <QTRSensors.h>
QTRSensors qtra; // Objeto para manejar el array de sensores QTR
// Configuración del número de sensores (8 sensores en total)
const uint8_t SensorCount = 8;
uint16_t sensorValues[SensorCount]; // Array para almacenar lecturas de sensores
// MAPEO DE PINES PARA DRIVER DE MOTORES TB6612FNG
#define STBY 3 // Pin de standby del driver (HIGH = activo)
#define AIN1 8 // Control de dirección motor A (derecho)
#define AIN2 9 // Control de dirección motor A (derecho)
#define PWMB 6 // Control PWM motor B (izquierdo)
#define PWMA 5 // Control PWM motor A (derecho)
#define BIN1 4 // Control de dirección motor B (izquierdo)
#define BIN2 7 // Control de dirección motor B (izquierdo)
#define LED 12 // LED indicador de estados
#define LED2 13
#define PINBOTON 2 // Botón para iniciar calibración y operación (PULL-UP)
#define PINBUZZER 10 // Buzzer para señales auditivas
// CONSTANTES DEL CONTROLADOR PID
float KP = 0.1; // Ganancia proporcional - respuesta inmediata al error
float KD = 0.5; // Ganancia derivativa - predice cambios futuros
float Ki = 0.0006; // Ganancia integral - corrige errores acumulados
int Velmax = 250; // Velocidad máxima de los motores (0-255)
// CONSTANTE PARA VELOCIDAD DE BÚSQUEDA (simplificación)
const int VELOCIDAD_BUSQUEDA = 200; // Velocidad fija para búsqueda
// VARIABLES PARA IMPLEMENTAR TÉRMINO INTEGRAL CON HISTORIAL
int error1 = 0; // Error más reciente
int error2 = 0; // Error anterior 1
int error3 = 0; // Error anterior 2
int error4 = 0; // Error anterior 3
int error5 = 0; // Error anterior 4
int error6 = 0; // Error más antiguo - mantiene 6 muestras para el integral
// FUNCIÓN PARA DETECTAR CURVA DE 90° A LA IZQUIERDA
bool shouldTurnLeft() {
// MAPEO: SENSOR 0 = EXTREMO DERECHO, SENSOR 7 = EXTREMO IZQUIERDO
// Lógica: Para curva izquierda, sensores derechos ven blanco, izquierdos ven línea
if (sensorValues[0] < 100 && sensorValues[1] < 100 && // Extremo derecho ve superficie blanca
sensorValues[6] > 401 && sensorValues[7] > 401) { // Extremo izquierdo ve línea negra
return true; // Confirma presencia de curva 90° izquierda
}
return false; // No hay curva detectada
}
// FUNCIÓN PARA DETECTAR BIFURCACIÓN Y DECIDIR TOMAR RAMA IZQUIERDA
bool shouldTakeLeftBranch() {
// BIFURCACIÓN: Múltiples sensores ven línea simultáneamente
// Condición: lado izquierdo ve línea, lado derecho ve blanco (favorece rama izquierda)
if (sensorValues[6] > 600 && // Centro-izquierdo detecta línea (umbral alto)
sensorValues[7] > 600 && // Extremo izquierdo detecta línea
sensorValues[5] > 600 && // Otro sensor centro-izquierdo detecta línea
sensorValues[0] < 400) { // Extremo derecho ve blanco (no hay rama derecha)
return true; // Condiciones para tomar bifurcación izquierda
}
return false; // No hay bifurcación izquierda o condiciones no se cumplen
}
// FUNCIÓN PARA CONTROLAR MOTOR IZQUIERDO (MOTOR B)
void Motoriz(int value) {
if (value >= 0) { // Movimiento hacia adelante
digitalWrite(BIN1, HIGH); // Configuración para giro adelante
digitalWrite(BIN2, LOW); // BIN1=HIGH, BIN2=LOW = adelante
} else { // Movimiento hacia atrás
digitalWrite(BIN1, LOW); // Configuración para giro atrás
digitalWrite(BIN2, HIGH); // BIN1=LOW, BIN2=HIGH = atrás
value *= -1; // Convertir valor negativo a positivo para PWM
}
analogWrite(PWMB, value); // Aplicar velocidad (0-255) al motor izquierdo
}
// FUNCIÓN PARA CONTROLAR MOTOR DERECHO (MOTOR A)
void Motorde(int value) {
if (value >= 0) { // Movimiento hacia adelante
digitalWrite(AIN1, HIGH); // Configuración para giro adelante
digitalWrite(AIN2, LOW); // AIN1=HIGH, AIN2=LOW = adelante
} else { // Movimiento hacia atrás
digitalWrite(AIN1, LOW); // Configuración para giro atrás
digitalWrite(AIN2, HIGH); // AIN1=LOW, AIN2=HIGH = atrás
value *= -1; // Convertir valor negativo a positivo para PWM
}
analogWrite(PWMA, value); // Aplicar velocidad (0-255) al motor derecho
}
// FUNCIÓN PARA CONTROLAR AMBOS MOTORES SIMULTÁNEAMENTE
void Motor(int left, int right) {
digitalWrite(STBY, HIGH); // Activar driver de motores (salir de standby)
Motoriz(left); // Configurar motor izquierdo con velocidad 'left'
Motorde(right); // Configurar motor derecho con velocidad 'right'
}
void setup() {
Serial.begin(9600); // Inicializar comunicación serial para debug
// CONFIGURACIÓN DE PINES COMO SALIDAS
pinMode(LED, OUTPUT); // LED indicador
pinMode(LED2, OUTPUT); // LED2 indicador
digitalWrite(LED, HIGH); // Encender LED al inicio
digitalWrite(LED2, LOW); // LED2 apagado al inicio
pinMode(BIN2, OUTPUT); // Dirección motor izquierdo
pinMode(STBY, OUTPUT); // Standby del driver
pinMode(BIN1, OUTPUT); // Dirección motor izquierdo
pinMode(PWMB, OUTPUT); // PWM motor izquierdo
pinMode(AIN1, OUTPUT); // Dirección motor derecho
pinMode(AIN2, OUTPUT); // Dirección motor derecho
pinMode(PWMA, OUTPUT); // PWM motor derecho
pinMode(PINBUZZER, OUTPUT); // Buzzer como salida
// CONFIGURACIÓN DEL BOTÓN CON PULL-UP INTERNO
pinMode(PINBOTON, INPUT_PULLUP); // Botón con resistencia pull-up interna activada
// CONFIGURACIÓN DEL ARRAY DE SENSORES QTR
qtra.setTypeAnalog(); // Configurar sensores como analógicos
// Mapear pines analógicos A0-A7 a los 8 sensores
qtra.setSensorPins((const uint8_t[]){A0, A1, A2, A3, A4, A5, A6, A7}, SensorCount);
qtra.setEmitterPin(11); // Pin que controla LEDs infrarrojos de los sensores
// ESPERAR PRESIÓN DE BOTÓN PARA INICIAR CALIBRACIÓN
WaitBoton();
// PROCESO DE CALIBRACIÓN AUTOMÁTICA (70 muestras)
for (uint16_t i = 0; i < 70; i++) {
digitalWrite(LED, HIGH); // LED ON durante calibración
delay(20); // Pausa entre muestras
qtra.calibrate(); // Tomar muestra de calibración
digitalWrite(LED, LOW); // LED OFF
delay(20); // Pausa entre muestras
}
// SEÑALES AUDITIVAS DE CONFIRMACIÓN
tone(PINBUZZER, 1500, 50); // Primer beep
delay(70);
tone(PINBUZZER, 1500, 50); // Segundo beep - calibración completa
delay(70);
// ESPERAR SEGUNDA PRESIÓN DE BOTÓN PARA INICIAR OPERACIÓN
WaitBoton();
}
// VARIABLES DEL CONTROLADOR PID
int proporcional = 0; // Error actual (diferencia entre posición deseada y real)
int integral = 0; // Suma de errores pasados (corrige offset permanente)
int derivativo = 0; // Cambio del error (predice tendencia)
int diferencial = 0; // Salida final del PID (corrección a aplicar)
int last_prop = 0; // Error anterior para calcular derivativo - INICIALIZADO CORRECTAMENTE
int Target = 3500; // Posición objetivo (centro del array de sensores)
void loop() {
// LEER POSICIÓN DE LA LÍNEA (0-7000, donde 3500 = centro)
uint16_t position = qtra.readLineBlack(sensorValues);
// DETECCIÓN DE BIFURCACIÓN PRIORITARIA
if (shouldTakeLeftBranch()) {
// BIFURCACIÓN DETECTADA - ejecutar maniobra de giro pronunciado
digitalWrite(LED, HIGH); // LED ON indica maniobra especial
//Motor(240, 0); // Motor izq a 240, derecho parado = giro derecha fuerte
//delay(50); // Mantener giro por 50ms para comprometerse con rama
}
// DETECCIÓN DE CURVA 90° (solo si no hay bifurcación)
else if (shouldTurnLeft()) {
// CURVA 90° DETECTADA - ejecutar giro normal
//digitalWrite(LED, LOW); // LED OFF indica giro normal
//Motor(240, 0); // Motor izq a 240, derecho parado = giro derecha
//delay(50); // Mantener giro por 50ms
}
else {
// CONTROL PID NORMAL - solo cuando no hay maniobras especiales
digitalWrite(LED, LOW); // LED OFF indica operación normal
// CALCULAR ERROR PROPORCIONAL
proporcional = ((int)position) - Target; // Error = posición_actual - posición_deseada
// SISTEMA DE BÚSQUEDA ACTIVA SIMPLIFICADO
if (proporcional <= -Target) { // Línea perdida hacia la DERECHA (position = 0)
// BUSCAR GIRANDO DERECHA: motor izq parado, derecho a velocidad fija
Motor(0, VELOCIDAD_BUSQUEDA); // Velocidad de búsqueda constante (200)
} else if (proporcional >= Target) { // Línea perdida hacia la IZQUIERDA (position = 7000)
// BUSCAR GIRANDO IZQUIERDA: motor izq a velocidad fija, derecho parado
Motor(VELOCIDAD_BUSQUEDA, 0); // Velocidad de búsqueda constante (200)
}
else {
// CONTROL PID NORMAL - EJECUTA SOLO CUANDO NO ESTÁ EN BÚSQUEDA
// CALCULAR TÉRMINO DERIVATIVO (predice tendencia del error)
derivativo = proporcional - last_prop;
// CALCULAR TÉRMINO INTEGRAL CON ANTI-WINDUP (suma errores pasados)
integral = error1 + error2 + error3 + error4 + error5 + error6;
// ANTI-WINDUP: Resetear integral cuando robot cruza la línea
// Detecta cambio de signo del error (cruzó de un lado al otro)
if ((proporcional > 0 && last_prop < 0) || (proporcional < 0 && last_prop > 0)) {
integral = 0; // Resetear integral - elimina "memoria tóxica"
// Limpiar historial completo para empezar fresco
error1 = error2 = error3 = error4 = error5 = error6 = 0;
}
// LIMITAR INTEGRAL para evitar saturación y sobrecorrección
integral = constrain(integral, -1000, 1000);
// Guardar error actual para próximo cálculo derivativo
last_prop = proporcional;
// ACTUALIZAR HISTORIAL DE ERRORES (registro circular)
error6 = error5; // El más antiguo se descarta
error5 = error4;
error4 = error3;
error3 = error2;
error2 = error1;
error1 = proporcional; // El más reciente entra
// CÁLCULO FINAL PID: combinar los tres términos
diferencial = (proporcional * KP) + (derivativo * KD) + (integral * Ki);
// LIMITAR SALIDA PID a rangos seguros del motor
if (diferencial > Velmax) diferencial = Velmax; // No exceder velocidad máxima
if (diferencial < -Velmax) diferencial = -Velmax; // No exceder en reversa
// APLICAR CORRECCIÓN DIFERENCIAL A LOS MOTORES
if (diferencial < 0) {
// Error negativo: robot va hacia izquierda, corregir hacia derecha
// Reducir velocidad motor izquierdo, mantener derecho al máximo
Motor(Velmax + diferencial, Velmax); // diferencial negativo reduce vel izq
} else {
// Error positivo: robot va hacia derecha, corregir hacia izquierda
// Mantener motor izquierdo al máximo, reducir derecho
Motor(Velmax, Velmax - diferencial); // diferencial positivo reduce vel der
}
}
}
}
// FUNCIÓN QUE ESPERA PRESIÓN DEL BOTÓN CON CONFIRMACIÓN AUDITIVA
// CONFIGURACIÓN PULL-UP: Estado de reposo = HIGH, presionado = LOW
void WaitBoton() {
// Esperar a que el botón NO esté presionado inicialmente (estado estable)
while (digitalRead(PINBOTON) == LOW) {
delay(10); // Si está presionado al inicio, esperar que se suelte
}
// Ahora esperar a que se presione el botón
while (digitalRead(PINBOTON) == HIGH) {
delay(10); // Bucle hasta que botón se presione (cambie a LOW)
}
// Debounce: esperar un momento para estabilizar la lectura
delay(50);
// Confirmar que sigue presionado (evitar falsos positivos por ruido)
if (digitalRead(PINBOTON) == LOW) {
// Esperar que se suelte el botón para evitar múltiples activaciones
while (digitalRead(PINBOTON) == LOW) {
delay(10);
}
delay(50); // Debounce adicional al soltar
tone(PINBUZZER, 2000, 100); // Beep de confirmación
}
}
Comentarios
Publicar un comentario