123 lines
6.6 KiB
Markdown
123 lines
6.6 KiB
Markdown
# Hallazgos — sesión 2026-04-15
|
||
|
||
## Resumen ejecutivo
|
||
|
||
Se construyó un sandbox bajo `test/test_circuit/` que valida end-to-end la emulación de circuitos analógicos + digitales + Arduino real vía `avr8js`. **25 tests pasan** en ~2.9 s. La propuesta del `plan1.md` (ngspice WASM, 40 MB) puede **reemplazarse por un solver MNA en JS puro** de ~500 líneas con resultados correctos en los casos probados.
|
||
|
||
## Lo que funciona
|
||
|
||
### Solver DC/transitorio (src/solver/)
|
||
|
||
- **MNA (Modified Nodal Analysis)** con eliminación gaussiana de pivote parcial.
|
||
- Ground implícito (no aparece en la matriz).
|
||
- Ruido numérico controlado con `G_min = 1e-12 S` en diagonal.
|
||
- **Newton-Raphson** iterativo para no-lineales (diodo / LED / BJT), con:
|
||
- límite SPICE-style `pnjlim` para evitar `exp()` overflow en la primera iteración.
|
||
- damping adaptativo (max 0.5 V por paso por nodo).
|
||
- convergencia < 1 µV en 3–8 iteraciones típicas.
|
||
- **Transitorio** con backward Euler (método de companion para el capacitor). La clave: **no ejecutar `solveDC()` inicial antes del primer paso**, porque con capacitores abiertos en DC se calcula un estado falso que luego queda como `V_prev`. En su lugar, sembrar `prev` desde `Vinit`.
|
||
|
||
### Componentes implementados
|
||
|
||
| Categoría | Componentes |
|
||
|---|---|
|
||
| Pasivos lineales | Resistor, VoltageSource, CurrentSource, Potentiometer, Switch |
|
||
| Pasivos dinámicos | Capacitor (backward Euler) |
|
||
| No-lineales | Diode (Shockley), LED (5 colores), BJT_NPN (Ebers-Moll simplificado) |
|
||
| Paramétricos | NTCThermistor (modelo β) |
|
||
|
||
### Integración avr8js ≡ Velxio
|
||
|
||
Se copió fielmente el patrón de Velxio:
|
||
- `new CPU(program, 8192)`, `new AVRIOPort(cpu, portXConfig)`, `new AVRADC(cpu, adcConfig)`, `new AVRTimer(cpu, timerXConfig)`.
|
||
- Listeners por puerto → mapeo `(portName, bit) → Arduino pin`.
|
||
- `adc.channelValues[ch] = volts` para inyectar voltajes en tiempo real.
|
||
- `cpu.data[ocrAddr]` para leer duty de PWM.
|
||
|
||
El test `avr_blink.test.js` carga la **misma `.hex` que usa Velxio** y dispara el mismo LED simulado con corriente real calculada por el solver. Funciona.
|
||
|
||
### E2E: potenciómetro → PWM → LED
|
||
|
||
| Wiper | V_A0 | PWM duty | I_LED | Brillo |
|
||
|---|---|---|---|---|
|
||
| 0 % | 0.00 V | 0.0 % | 0.00 mA | 0.000 |
|
||
| 25 % | 1.25 V | 24.7 % | 0.00 mA | 0.000 |
|
||
| 50 % | 2.50 V | 49.8 % | 3.26 mA | 0.163 |
|
||
| 75 % | 3.75 V | 74.9 % | 8.77 mA | 0.439 |
|
||
| 100 % | 5.00 V | 100.0 % | 14.38 mA | 0.719 |
|
||
|
||
El PWM duty cycle rastrea el wiper con < 1 % de error. La no-linealidad I–V del LED emerge naturalmente (por debajo de su knee no conduce).
|
||
|
||
### E2E: termistor NTC
|
||
|
||
Error máximo de recuperación de temperatura: **0.05 °C** en el rango 0-50 °C (β=3950). Prácticamente el error de redondeo del ADC de 10 bits.
|
||
|
||
## Lo que no se probó / gaps
|
||
|
||
- **Inductores** no se implementaron (no había casos de uso en el scope).
|
||
- **MOSFET** queda para una futura iteración (BJT alcanzó).
|
||
- **Análisis AC/frecuencia** (Bode, resonancia) — fuera del scope.
|
||
- **Co-simulación ciclo-a-ciclo** entre AVR y solver. Los tests resuelven el circuito una vez después de cada cambio de pin, no en cada ciclo. Para PWM se usa `duty · V_supply` como fuente equivalente, no la conmutación real → precisión aceptable para brillo medio, pero no modela ripple.
|
||
|
||
## Problemas encontrados (y solución)
|
||
|
||
### 1. Diodo explotaba en la primera iteración de Newton
|
||
- **Síntoma**: `Vd` saltaba a 5 V (apertura circuital cuando `gd ≈ 0` en iter 0). Siguiente iter: `exp(5/0.026) ≈ 1e84` → NaN.
|
||
- **Fix**: `pnjlim` dentro del stamp del diodo. Primera iteración: `Vd = min(Vd, Vcrit)` donde `Vcrit = nVt · ln(nVt / (√2 · Is))`. Iteraciones siguientes: paso logarítmico si `|ΔVd| > 2nVt`.
|
||
|
||
### 2. Capacitor no cargaba
|
||
- **Síntoma**: `V(τ)` era 5 V en vez de 3.16 V. El `solveDC()` inicial veía el cap abierto → `Vout=5V` → este estado se usaba como `V_prev`.
|
||
- **Fix**: `runTransient()` ahora siembra `state.prev` desde `capacitor.Vinit` directamente, sin llamar a `solveDC()` inicial.
|
||
|
||
### 3. ADC daba valores 0-3 en vez de 0-255
|
||
- **Síntoma**: leer `ADCH` con `ADMUX = 0x40` retornaba solo los top-2 bits del resultado 10-bit (derecha-ajustado por defecto).
|
||
- **Fix**: activar `ADLAR=1` → `ADMUX = 0x60`. Ahora `ADCH` contiene los 8 MSB.
|
||
|
||
### 4. Offsets RJMP incorrectos en programa hand-assembled
|
||
- **Síntoma**: loop saltaba a dirección equivocada tras añadir más instrucciones.
|
||
- **Fix**: cálculo manual cuidadoso. Cada `STS`/`LDS` ocupa 2 words. `RJMP k` salta a `PC+1+k`.
|
||
|
||
## Mejoras sugeridas para Velxio (main app)
|
||
|
||
1. **Adoptar este solver** para la simulación eléctrica en lugar de `ngspice-wasm`. Ventajas:
|
||
- 0 KB de dependencia WASM (vs 40 MB)
|
||
- Inicialización instantánea
|
||
- Fácil extender con componentes custom de wokwi-elements
|
||
|
||
2. **LED brightness realista**. Actualmente Velxio muestra LEDs como on/off booleanos. Con el solver:
|
||
- `led.brightness(state)` ∈ [0, 1] basado en corriente directa
|
||
- El ajuste del brillo del componente wokwi-led puede hacerse con `--wokwi-led-brightness` CSS var o similar.
|
||
|
||
3. **Potenciómetro funcional**. El componente ya existe visualmente pero el wiper no afecta a nada. Con este solver:
|
||
- `pot.setWiper(x)` → recalcular → inyectar V en `adc.channelValues[ch]`
|
||
- Habilitar drag interactivo del wiper.
|
||
|
||
4. **Warnings al usuario**:
|
||
- Si la corriente por un LED > 25 mA → overlay rojo "quemaría el LED".
|
||
- Si un diodo queda en reverse breakdown → overlay amarillo.
|
||
- Si no hay resistencia en serie con un LED → advertencia pre-simulación.
|
||
|
||
5. **Corridas en Web Worker** si el circuito supera ~50 componentes, para no bloquear la UI. El solver como está es ~3 ms por `solveDC()` con 10 componentes en Node → debería ser < 30 ms con 100 en browser.
|
||
|
||
## Performance (measurements en WSL2, Node 22)
|
||
|
||
| Suite | Tests | Duración |
|
||
|---|---|---|
|
||
| passive.test.js | 11 | 8 ms |
|
||
| transient_rc.test.js | 3 | 22 ms |
|
||
| diodes.test.js | 7 | 13 ms |
|
||
| avr_blink.test.js | 2 | 2.4 s (loop CPU real) |
|
||
| e2e_pot_pwm_led.test.js | 1 | 90 ms |
|
||
| e2e_thermistor.test.js | 1 | 121 ms |
|
||
| **Total** | **25** | **~2.9 s** |
|
||
|
||
Prácticamente todo el tiempo se va en avr8js ejecutando los 32 M de ciclos del blink. El solver de circuitos es irrelevante en el presupuesto.
|
||
|
||
## Próximos pasos recomendados
|
||
|
||
1. **Integrar** un wrapper del solver en `frontend/src/simulation/` (nuevo archivo `CircuitSolver.ts`).
|
||
2. **Conectar** con `useSimulatorStore.wires` y `components` para auto-construir el `Circuit` objeto.
|
||
3. **Hook** en `PinManager.onPinChange()` con debounce de 50 ms → `circuit.solveDC()`.
|
||
4. **Reutilizar** las fixtures de HEX que ya tiene `frontend/src/__tests__/fixtures/`.
|
||
5. **Añadir** MOSFET, Zener, op-amp ideal (GMIN stamping) cuando haya demanda.
|