Files
AR-Autopilot/docs/firmware-libraries-research.md
T
alro65 1d7dd63327 docs(sprint-1): proposed plan + firmware libraries research
Two new documents to drive the Sprint 1 approval cycle when the user
reviews in the morning. NO firmware code touched -- this is planning
material only, per brief rule #1 "Antes de cada sprint, me presentas
plan detallado y esperas mi OK. No improvises features."

docs/sprint-1-plan.md
  - Sprint 1 objective (firmware boot + STANDBY + Modbus slave +
    NMEA 2000 consume of PGN 127250/127251 + watchdog).
  - 4 explicit technical decisions awaiting the user's go/no-go:
      2.1 Framework: Arduino-as-ESP-IDF-component (recommended)
      2.2 New libraries: NMEA2000-library, NMEA2000_esp32, eModbus
          (asked per brief rule #4 "No agregues dependencias sin
          preguntarme")
      2.3 FreeRTOS core/priority mapping (PID isolated on Core 1)
      2.4 Logging: ESP_LOG via UART0
  - 7-phase breakdown over 3-4 weeks (mirrors brief's Sprint 1 scope).
  - Acceptance criteria, risks, and the inputs needed from the user
    before kickoff (hardware availability, schematic, NMEA 2000 bus).

docs/firmware-libraries-research.md
  - Detailed rationale for each library choice with comparison tables.
  - Pinout / hardware references aligned with the existing
    firmware/ar_autopilot_v1/src/hal/pinout.h.
  - Draft platformio.ini outline (NOT yet written to the firmware
    directory -- needs user OK first).
  - All external sources cited for traceability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 07:37:53 -04:00

9.6 KiB

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 Stack core (parsing, PGNs, NMEA2000 class). Independiente de hardware
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):

#include <NMEA2000_CAN.h>     // selects ESP32 driver automatically
#include <N2kMessages.h>

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
Secure boot + flash encryption ⚠️ limitado
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. Permite usar libs Arduino dentro de un proyecto ESP-IDF "puro". Es exactamente lo que necesitamos.

Configuración en PlatformIO (resumen):

[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