Funciona todo sin modulo_imu

This commit is contained in:
2026-05-21 09:51:42 -05:00
parent 026820f205
commit 19dc4bcf90
19 changed files with 1857 additions and 0 deletions
+395
View File
@@ -0,0 +1,395 @@
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include <Arduino.h>
#include <cmath>
// ==========================================
// PROTOCOLO UART
// ==========================================
//
// RX (RPi → ESP32):
// M:s1,f1,s2,f2,s3,f3,s4,f4\n
// s = signo → -1 | 0 | 1
// f = fracción 0.000–1.000 de VEL_MAX_TICKS del motor
// S:\n → Stop inmediato
// C:\n → Iniciar autocalibración
//
// TX (ESP32 → RPi):
// T:t1,t2,t3,t4\n → Odometría acumulada (ticks con signo)
// K:v1,v2,v3,v4\n → VEL_MAX_TICKS de cada motor (ticks/s) — tras calibrar
// D:...\n → Debug (solo por USB/Serial)
//
// ==========================================
// ─── FLAGS ────────────────────────────────────────────────────────────────────
volatile bool iniciar_calibracion = false;
volatile bool en_calibracion = false;
// ─── PINES ────────────────────────────────────────────────────────────────────
const int PIN_RX_RPI = 44, PIN_TX_RPI = 43;
const int PIN_STBY_1 = 40, PIN_STBY_2 = 7;
const int PIN_M1_IN1 = 15, PIN_M1_IN2 = 16, PIN_M1_PWM = 17, PIN_ENC1 = 9;
const int PIN_M2_IN1 = 6, PIN_M2_IN2 = 5, PIN_M2_PWM = 4, PIN_ENC2 = 10;
const int PIN_M3_IN1 = 41, PIN_M3_IN2 = 42, PIN_M3_PWM = 2, PIN_ENC3 = 36;
const int PIN_M4_IN1 = 39, PIN_M4_IN2 = 38, PIN_M4_PWM = 37, PIN_ENC4 = 35;
const int FREC_PWM = 1000, RES_PWM = 8;
const int IN1[] = {PIN_M1_IN1, PIN_M2_IN1, PIN_M3_IN1, PIN_M4_IN1};
const int IN2[] = {PIN_M1_IN2, PIN_M2_IN2, PIN_M3_IN2, PIN_M4_IN2};
const int PWM[] = {PIN_M1_PWM, PIN_M2_PWM, PIN_M3_PWM, PIN_M4_PWM};
const int ENC[] = {PIN_ENC1, PIN_ENC2, PIN_ENC3, PIN_ENC4};
// ─── ENCODERS ─────────────────────────────────────────────────────────────────
volatile unsigned long pulsos[4] = {0, 0, 0, 0};
void IRAM_ATTR isr_E1() { pulsos[0]++; }
void IRAM_ATTR isr_E2() { pulsos[1]++; }
void IRAM_ATTR isr_E3() { pulsos[2]++; }
void IRAM_ATTR isr_E4() { pulsos[3]++; }
long global_ticks[4] = {0, 0, 0, 0};
SemaphoreHandle_t mutexDatos;
// ─── VELOCIDAD MÁXIMA CALIBRADA ───────────────────────────────────────────────
// En ticks/s. La RPi la pide con C: y la recibe en K:
float VEL_MAX_TICKS[4] = {200.0f, 200.0f, 200.0f, 200.0f};
// Banda muerta por motor = 3% de VEL_MAX (se recalcula tras calibrar)
float BANDA_MUERTA[4] = {6.0f, 6.0f, 6.0f, 6.0f};
// ==========================================
// CLASE PID
// ==========================================
class PID {
public:
float kp, ki, kff;
float error_acumulado = 0.0f;
float setpoint = 0.0f; // ticks/s, con signo
PID(float _kp, float _ki, float _kff) : kp(_kp), ki(_ki), kff(_kff) {}
// vel_actual: ticks/s con signo
// banda : umbral bajo el cual el error se ignora (ticks/s)
int calcularPWM(float vel_actual, float dt, float banda) {
if (fabsf(setpoint) < 1.0f) {
error_acumulado = 0.0f;
return 0;
}
float error = setpoint - vel_actual;
// Banda muerta solo en régimen estacionario (motor ya girando)
if (fabsf(error) < banda && fabsf(vel_actual) > 5.0f)
error = 0.0f;
error_acumulado += error * dt;
float lim = 200.0f / (ki + 0.0001f);
error_acumulado = constrain(error_acumulado, -lim, lim);
float salida = kp * error
+ ki * error_acumulado
+ copysignf(fabsf(setpoint) * kff, setpoint);
return (int)constrain(salida, -255.0f, 255.0f);
}
void reset() { error_acumulado = 0.0f; setpoint = 0.0f; }
};
PID pid[4] = {
PID(2.0f, 1.5f, 0.0f), // M1 FL
PID(2.0f, 1.5f, 0.0f), // M2 FR
PID(2.0f, 1.5f, 0.0f), // M3 RL
PID(2.0f, 1.5f, 0.0f), // M4 RR
};
// ─── MOTOR ────────────────────────────────────────────────────────────────────
void moverMotor(int idx, int val) {
if (abs(val) < 15) {
digitalWrite(IN1[idx], LOW);
digitalWrite(IN2[idx], LOW);
ledcWrite(PWM[idx], 0);
return;
}
if (val > 0) { digitalWrite(IN1[idx], HIGH); digitalWrite(IN2[idx], LOW); }
else { digitalWrite(IN1[idx], LOW); digitalWrite(IN2[idx], HIGH); val = -val; }
ledcWrite(PWM[idx], val);
}
void pararTodos() {
for (int i = 0; i < 4; i++) moverMotor(i, 0);
}
// ─── LECTURA ATÓMICA DE ENCODERS ──────────────────────────────────────────────
// portENTER_CRITICAL_ISR bloquea ambos cores en ESP32 (a diferencia de noInterrupts)
static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
inline void leerPulsos(unsigned long dest[4]) {
portENTER_CRITICAL(&mux);
for (int i = 0; i < 4; i++) dest[i] = pulsos[i];
portEXIT_CRITICAL(&mux);
}
// ==========================================
// TAREA: CONTROL DE MOTORES (CORE 1)
// ==========================================
void tareaControlMotores(void *pvParameters) {
unsigned long p_ant[4] = {0, 0, 0, 0};
static float v_f[4] = {0, 0, 0, 0};
const float alpha = 0.35f;
const float dt = 0.05f;
const TickType_t xFrec = pdMS_TO_TICKS(50);
TickType_t xUltimo = xTaskGetTickCount();
for (;;) {
// ────────────────────────────────────────────────────────────────────────
// AUTOCALIBRACIÓN
// ────────────────────────────────────────────────────────────────────────
if (iniciar_calibracion) {
en_calibracion = true;
for (int i = 0; i < 4; i++) pid[i].reset();
pararTodos();
vTaskDelay(pdMS_TO_TICKS(500));
unsigned long p_base[4];
leerPulsos(p_base);
for (int i = 0; i < 4; i++) p_ant[i] = p_base[i];
// — Paso 1: fricción estática ————————————————————————————————————————
int friccion[4] = {0, 0, 0, 0};
for (int p = 20; p <= 130; p += 2) {
for (int i = 0; i < 4; i++)
if (friccion[i] == 0) {
digitalWrite(IN1[i], LOW);
digitalWrite(IN2[i], HIGH);
ledcWrite(PWM[i], p);
}
vTaskDelay(pdMS_TO_TICKS(60));
unsigned long p_act[4];
leerPulsos(p_act);
bool todos = true;
for (int i = 0; i < 4; i++) {
if (friccion[i] == 0) {
if ((p_act[i] - p_ant[i]) > 1) friccion[i] = p;
else todos = false;
}
p_ant[i] = p_act[i];
}
if (todos) break;
}
// — Paso 2: velocidad máxima (kff y VEL_MAX_TICKS) ——————————————————
const int TEST_PWM = 200;
for (int i = 0; i < 4; i++) {
digitalWrite(IN1[i], LOW);
digitalWrite(IN2[i], HIGH);
ledcWrite(PWM[i], TEST_PWM);
}
vTaskDelay(pdMS_TO_TICKS(800)); // estabilización
unsigned long p_ss1[4]; leerPulsos(p_ss1);
vTaskDelay(pdMS_TO_TICKS(300));
unsigned long p_ss2[4]; leerPulsos(p_ss2);
for (int i = 0; i < 4; i++) {
float vel_medida = (float)(p_ss2[i] - p_ss1[i]) / 0.3f; // ticks/s
// VEL_MAX extrapolada a PWM 255
VEL_MAX_TICKS[i] = (vel_medida > 10.0f)
? vel_medida * (255.0f / (float)(TEST_PWM - friccion[i]))
: 200.0f;
VEL_MAX_TICKS[i] = constrain(VEL_MAX_TICKS[i], 50.0f, 1000.0f);
// kff: PWM por ticks/s
pid[i].kff = (vel_medida > 10.0f)
? (float)(TEST_PWM - friccion[i]) / vel_medida
: 2.0f;
pid[i].kff = constrain(pid[i].kff, 0.1f, 8.0f);
// Banda muerta = 3% de VEL_MAX
BANDA_MUERTA[i] = VEL_MAX_TICKS[i] * 0.03f;
pid[i].reset();
}
pararTodos();
// Reporte por USB
Serial.println("=== CALIBRACION COMPLETA ===");
for (int i = 0; i < 4; i++)
Serial.printf("M%d fric=%d vel_max=%.1f kff=%.3f banda=%.1f\n",
i+1, friccion[i], VEL_MAX_TICKS[i], pid[i].kff, BANDA_MUERTA[i]);
// ── Trama K: → RPi para que actualice su VEL_MAX ──────────────────────
// Tanto por USB como por Serial1 (UART RPi)
char buf[80];
snprintf(buf, sizeof(buf), "K:%.1f,%.1f,%.1f,%.1f\n",
VEL_MAX_TICKS[0], VEL_MAX_TICKS[1],
VEL_MAX_TICKS[2], VEL_MAX_TICKS[3]);
Serial.print(buf);
Serial1.print(buf);
leerPulsos(p_ant);
iniciar_calibracion = false;
en_calibracion = false;
}
// ────────────────────────────────────────────────────────────────────────
// Leer encoders
unsigned long p_act[4];
leerPulsos(p_act);
unsigned long d_p[4];
for (int i = 0; i < 4; i++) {
d_p[i] = p_act[i] - p_ant[i];
p_ant[i] = p_act[i];
}
// Dirección del setpoint para acumular odometría con signo
int d_f[4];
for (int i = 0; i < 4; i++)
d_f[i] = (pid[i].setpoint > 1.0f) ? 1 :
(pid[i].setpoint < -1.0f) ? -1 : 0;
if (xSemaphoreTake(mutexDatos, pdMS_TO_TICKS(5)) == pdTRUE) {
for (int i = 0; i < 4; i++)
global_ticks[i] += (long)d_p[i] * d_f[i];
xSemaphoreGive(mutexDatos);
}
// Velocidad filtrada con EMA
for (int i = 0; i < 4; i++) {
float v_cruda = ((float)d_p[i] * (float)d_f[i]) / dt;
v_f[i] = alpha * v_cruda + (1.0f - alpha) * v_f[i];
}
// PID y actuación
for (int i = 0; i < 4; i++)
moverMotor(i, pid[i].calcularPWM(v_f[i], dt, BANDA_MUERTA[i]));
// Debug cada 500ms (10 ciclos × 50ms)
static int dbg = 0;
if (++dbg >= 10) {
dbg = 0;
Serial.printf("D:SP=%.0f,%.0f,%.0f,%.0f V=%.0f,%.0f,%.0f,%.0f\n",
pid[0].setpoint, pid[1].setpoint, pid[2].setpoint, pid[3].setpoint,
v_f[0], v_f[1], v_f[2], v_f[3]);
}
vTaskDelayUntil(&xUltimo, xFrec);
}
}
// ==========================================
// PARSEO DE TRAMA (Core 0)
// ==========================================
void procesarTrama(const String &t) {
if (t.length() < 2) return;
char cmd = t.charAt(0);
if (cmd == 'C') { iniciar_calibracion = true; return; }
if (cmd == 'S') { for (int i = 0; i < 4; i++) pid[i].reset(); return; }
if (cmd == 'M' && t.length() > 2) {
// Formato: M:s1,f1,s2,f2,s3,f3,s4,f4
// s = -1, 0, o 1 | f = 0.000–1.000
int s[4]; float f[4];
if (sscanf(t.c_str() + 2,
"%d,%f,%d,%f,%d,%f,%d,%f",
&s[0], &f[0], &s[1], &f[1],
&s[2], &f[2], &s[3], &f[3]) != 8)
return;
for (int i = 0; i < 4; i++) {
f[i] = constrain(f[i], 0.0f, 1.0f);
// Setpoint en ticks/s con signo aplicado directamente
pid[i].setpoint = (float)s[i] * f[i] * VEL_MAX_TICKS[i];
}
return;
}
}
// ==========================================
// TAREA: COMUNICACIÓN (Core 0)
// ==========================================
void tareaComunicacion(void *pvParameters) {
String tUSB = "", tRPI = "";
unsigned long uEnvio = 0;
unsigned long ultimaVezRecibido = millis();
for (;;) {
// Telemetría cada 40ms
if (millis() - uEnvio >= 40) {
uEnvio = millis();
long tk[4];
if (xSemaphoreTake(mutexDatos, pdMS_TO_TICKS(5)) == pdTRUE) {
for (int i = 0; i < 4; i++) tk[i] = global_ticks[i];
xSemaphoreGive(mutexDatos);
}
char buf[64];
snprintf(buf, sizeof(buf), "T:%ld,%ld,%ld,%ld\n", tk[0], tk[1], tk[2], tk[3]);
Serial.print(buf);
Serial1.print(buf);
}
// Lectura USB
while (Serial.available()) {
char c = Serial.read();
if (c == '\n') {
tUSB.trim();
if (tUSB.length()) { procesarTrama(tUSB); ultimaVezRecibido = millis(); }
tUSB = "";
} else { tUSB += c; }
}
// Lectura RPi
while (Serial1.available()) {
char c = Serial1.read();
if (c == '\n') {
tRPI.trim();
if (tRPI.length()) { procesarTrama(tRPI); ultimaVezRecibido = millis(); }
tRPI = "";
} else { tRPI += c; }
}
// Freno de emergencia (ignora calibración en curso)
// 800ms da margen para lags normales del SO de la RPi
if (!en_calibracion && millis() - ultimaVezRecibido > 800)
for (int i = 0; i < 4; i++) pid[i].reset();
vTaskDelay(pdMS_TO_TICKS(2));
}
}
// ==========================================
// SETUP
// ==========================================
void setup() {
Serial.begin(115200);
Serial1.begin(921600, SERIAL_8N1, PIN_RX_RPI, PIN_TX_RPI);
pinMode(PIN_STBY_1, OUTPUT); digitalWrite(PIN_STBY_1, HIGH);
pinMode(PIN_STBY_2, OUTPUT); digitalWrite(PIN_STBY_2, HIGH);
for (int i = 0; i < 4; i++) {
pinMode(IN1[i], OUTPUT);
pinMode(IN2[i], OUTPUT);
ledcAttach(PWM[i], FREC_PWM, RES_PWM);
pinMode(ENC[i], INPUT);
}
attachInterrupt(digitalPinToInterrupt(ENC[0]), isr_E1, RISING);
attachInterrupt(digitalPinToInterrupt(ENC[1]), isr_E2, RISING);
attachInterrupt(digitalPinToInterrupt(ENC[2]), isr_E3, RISING);
attachInterrupt(digitalPinToInterrupt(ENC[3]), isr_E4, RISING);
mutexDatos = xSemaphoreCreateMutex();
xTaskCreatePinnedToCore(tareaControlMotores, "Ctrl", 4096, NULL, 2, NULL, 1);
xTaskCreatePinnedToCore(tareaComunicacion, "Com", 4096, NULL, 1, NULL, 0);
}
void loop() { vTaskDelay(pdMS_TO_TICKS(10)); }