# Firmware libraries — research notes (Sprint 1 prep) Investigación realizada antes de planificar el Sprint 1 para elegir las librerías del firmware ESP32. Documento de referencia — no normativo, solo para que tengamos trazabilidad de por qué se eligió cada cosa. Fecha: 2026-05-18. --- ## 1. NMEA 2000 stack ### Decisión: `ttlappalainen/NMEA2000-library` + `ttlappalainen/NMEA2000_esp32` **Por qué es el estándar de facto:** - Es la stack NMEA 2000 más adoptada del ecosistema open-source para microcontroladores. Casi todos los proyectos NMEA 2000 de aficionados y muchos productos comerciales pequeños la usan. - Cubre tanto consumo como publicación de PGN. - Mantenida desde 2015, sigue activa. **Componentes:** | Repo | Rol | |---|---| | [`ttlappalainen/NMEA2000`](https://github.com/ttlappalainen/NMEA2000) | Stack core (parsing, PGNs, NMEA2000 class). Independiente de hardware | | [`ttlappalainen/NMEA2000_esp32`](https://github.com/ttlappalainen/NMEA2000_esp32) | Driver CAN específico para ESP32. Incluye su propio low-level driver — no requiere libs adicionales | **Configuración por defecto:** - TX pin: `GPIO 16` - RX pin: `GPIO 4` - Override: definir `ESP32_CAN_TX_PIN` y `ESP32_CAN_RX_PIN` antes de incluir los headers. **Framework support:** - ✅ Arduino framework - ✅ ESP-IDF (añadido oficialmente en 2022) - → Compatible con la decisión "Arduino-as-ESP-IDF-component" **Licencia:** MIT (compatible con producto comercial propietario). **Hardware externo necesario:** - Transceiver CAN físico: Microchip MCP2562, NXP TJA1051T, o similar. Conecta los pines GPIO16/4 del ESP32 al bus diferencial CAN-H/CAN-L del backbone NMEA 2000. El driver de la librería NO incluye el hardware del transceiver — es un chip aparte en la tarjeta AR-NMEA-IO. (Probablemente ya lo tienes en la v1.0 que diseñaste.) **PGN que vamos a consumir en Sprint 1:** | PGN | Nombre | Contenido relevante | |---|---|---| | 127250 | Vessel Heading | heading (rad), deviation, variation, reference (true/magnetic) | | 127251 | Rate of Turn | rate (rad/s) | **Snippet de referencia para Sprint 1 (no es código final):** ```cpp #include // selects ESP32 driver automatically #include void HandleHeading(const tN2kMsg &N2kMsg) { unsigned char SID; double Heading, Deviation, Variation; tN2kHeadingReference HeadingReference; if (ParseN2kHeading(N2kMsg, SID, Heading, Deviation, Variation, HeadingReference)) { // Heading in radians; convert to degrees for our use gLastHeadingDeg = Heading * 180.0 / M_PI; gLastHeadingTimestamp = millis(); } } void setup() { NMEA2000.SetN2kCANMsgBufSize(8); NMEA2000.SetMsgHandler(HandleHeading); // registers for ALL PGNs; // we filter inside the handler NMEA2000.Open(); } void loop() { NMEA2000.ParseMessages(); // call frequently from a dedicated task } ``` Esto es **orientativo**. En el sprint lo encapsulamos en una tarea FreeRTOS dedicada (`nmea2000_rx_task` en Core 0). --- ## 2. Modbus RTU server (slave) ### Decisión: `eModbus/eModbus` v1.7.4 **Comparación rápida:** | Lib | Slave RTU | Async/Tasks FreeRTOS | Mantenimiento | Veredicto | |---|---|---|---|---| | `eModbus/eModbus` v1.7.4 (2025-06) | ✅ | ✅ tasks separados, no bloquea | Activo | **Elegida** | | `emelianov/modbus-esp8266` (a.k.a. `modbus-esp32`) | ✅ | Parcial | Activo | Alternativa válida | | `MrMoses1911/ModbusRTU_ESP32` | ✅ | No documentado | Menor adopción | Descartada | | `ArduinoModbus` (oficial Arduino) | ✅ | No | Mantenida pero feature-pobre | Descartada | | `bertmelis/esp32ModbusRTU` | ❌ solo cliente | ✅ | Activo | No aplica (cliente, no slave) | **Por qué eModbus:** - Único que documenta explícitamente "Modbus communication is done in separate tasks" y API "non blocking / asynchronous" → el lazo PID no se bloquea jamás por tráfico Modbus. - Soporta RTU, ASCII y TCP — si en el futuro queremos pasar a Modbus TCP por Ethernet/WiFi, es la misma librería. - Versión estable reciente (junio 2025). - Licencia MIT. **Punto a verificar en Fase 1.3:** ⚠️ El README de eModbus no confirma explícitamente que la librería maneje sola la línea **DE/RE** (Driver Enable / Receiver Enable) del transceiver RS-485 MAX485 / MAX3485. Si no lo hace, escribimos un wrapper de ~30 líneas que toggle el `PIN_RS485_DE` del pinout antes y después de cada respuesta. **Framework support:** - ✅ Arduino framework (confirmado) - ❓ ESP-IDF nativo (no confirmado explícitamente; lo verifico al integrarlo) Si ESP-IDF nativo no está soportado: igual funciona desde el componente Arduino dentro de un proyecto ESP-IDF (eso es el patrón Arduino-as-ESP-IDF-component). **Mapa de registros propuesto** (a refinar en Sprint 1): | Tipo Modbus | Rango | Contenido | |---|---|---| | Holding (40001-) | 1-50 | Setpoints: heading_deseado, modo, comandos de operador | | Input (30001-) | 1-50 | Estado: heading_actual, rudder_angle_actual, modo_activo, alarmas activas | | Discrete (10001-) | 1-32 | Bits: engaged, alarms (uno por tipo), DI raw | | Coils (00001-) | 1-32 | Comandos digitales: engage_request, disengage_request, alarm_ack | El mapa exacto se finaliza al inicio de Fase 1.3 y se genera en single-source-of-truth desde un YAML. --- ## 3. Framework: ESP-IDF vs Arduino vs híbrido ### Decisión: Arduino-as-ESP-IDF-component **Resumen de la discusión:** | Aspecto | Arduino solo | ESP-IDF solo | Arduino-as-ESP-IDF-component | |---|---|---|---| | Manejo de FreeRTOS | Abstraído a single-thread | Nativo, control total | **Nativo (ESP-IDF), Arduino se compone encima** | | Disponibilidad de libs NMEA2000/Modbus | ✅ inmediata | ⚠️ requiere wrap | ✅ inmediata | | OTA con rollback automático | ❌ no | ✅ sí | ✅ sí | | Secure boot + flash encryption | ⚠️ limitado | ✅ sí | ✅ sí | | Determinismo PID real-time | ❌ unsuitable | ✅ excelente | ✅ excelente | | Production-grade (TWDT, panic handlers, partitions, NVS) | Limitado | Completo | Completo | | Complejidad inicial `platformio.ini` | Baja | Media | Media-alta (10 min de setup) | | Curva de aprendizaje | Cortísima | Media | Media | **Por qué para AR-Autopilot:** El brief es taxativo: > **Regla #11:** El PID corre en firmware ESP32, no en el display. Esto > es no-negociable: latencia determinística, seguridad ante caída del > display, eficiencia. > > **Sección 6 (frecuencias):** Lazo PID interno (timón a posición): 50 > Hz (20 ms) **Determinístico, sin jitter**. "Sin jitter" en 50 Hz exige FreeRTOS real con prioridades correctas, no el modelo abstraído de Arduino. Y al mismo tiempo no queremos perder 2 semanas reescribiendo NMEA2000_esp32 ni eModbus — son cientos de horas de trabajo de la comunidad. **Solución oficial Espressif:** [Arduino as an ESP-IDF component](https://docs.espressif.com/projects/arduino-esp32/en/latest/esp-idf_component.html). Permite usar libs Arduino dentro de un proyecto ESP-IDF "puro". Es exactamente lo que necesitamos. **Configuración en PlatformIO (resumen):** ```ini [env:esp32-arautopilot] platform = espressif32@~6.7.0 board = esp32dev ; ajustar al chip real (ESP32-DOWD) framework = arduino, espidf ; ← aquí está el truco ; Pinneamos versión del core Arduino para reproducibilidad platform_packages = framework-arduinoespressif32@~3.20017.0 lib_deps = ttlappalainen/NMEA2000-library ttlappalainen/NMEA2000_esp32 https://github.com/eModbus/eModbus.git#v1.7.4.stable build_flags = -DCORE_DEBUG_LEVEL=3 -DCONFIG_FREERTOS_HZ=1000 ; ... más flags al hacer el sprint monitor_speed = 115200 ``` **Riesgo conocido:** el modo dual `framework = arduino, espidf` a veces se rompe entre versiones de PlatformIO. Mitigación: pinneamos versión exacta del `platform` y de `framework-arduinoespressif32`. --- ## 4. Hardware externo / transceivers (referencia) Pinout `firmware/ar_autopilot_v1/src/hal/pinout.h` ya declara los pines lógicos. Para que el sistema físicamente funcione, la tarjeta AR-NMEA-IO debe tener: | Función | Chip típico | Pin ESP32 | |---|---|---| | Transceiver CAN (NMEA 2000) | MCP2562, TJA1051T | GPIO16 (TX), GPIO4 (RX) — overridable | | Transceiver RS-485 (Modbus) | MAX485, MAX3485, SP485E | GPIO21 (TX), GPIO22 (RX), GPIO23 (DE) | | Acondicionamiento AI1 (rudder sensor) | Op-amp + divisor / módulo 4-20 mA loop | GPIO36 (ADC1_CH0) | | Driver de potencia para DO1/DO2 (bomba) | Relé SSR o transistor + flyback | GPIO13, GPIO12 | Asumo que tu tarjeta v1.0 ya los lleva (es la misma usada en VMS-Sailor). Si hay alguna diferencia, lo cuadramos al inicio de Fase 1.2 con tu esquemático. --- ## 5. Fuentes consultadas - [NMEA2000_esp32 — GitHub](https://github.com/ttlappalainen/NMEA2000_esp32) - [NMEA2000 library — GitHub](https://github.com/ttlappalainen/NMEA2000) - [NMEA2000_esp32 — PlatformIO Registry](https://registry.platformio.org/libraries/ttlappalainen/NMEA2000_esp32) - [eModbus — GitHub](https://github.com/eModbus/eModbus) - [Arduino as ESP-IDF component — Espressif docs](https://docs.espressif.com/projects/arduino-esp32/en/latest/esp-idf_component.html) - [ESP-IDF vs Arduino Framework for ESP32 — Hubble Network Community](https://hubble.com/community/comparisons/esp-idf-vs-arduino-framework-for-esp32-when-to-switch-and-why/) - [ESP-IDF vs. Arduino for ESP32-S3: The Engineering Pivot to Production — Hoomanely Tech](https://tech.hoomanely.com/esp-idf-vs-arduino-for-esp32-s3-the-engineering-pivot-to-production/) - [Modbus RTU slave (RS485) for ESP32 on Arduino platform — PlatformIO Community](https://community.platformio.org/t/modbus-rtu-slave-rs485-for-esp32-on-arduino-platform/16196)