Support both Nano bootloaders for flashing over WiFi

- Uploader sends 'FLASH <baud>' first; the ESP32 restarts the Nano and
  bridges at that baud rate, then answers OK
- --baud=auto (default) tries 57600 (old ATmegaBOOT) then 115200 (Optiboot)
- Optional nano_reset GPIO (NPN to Nano RESET) pulses reset after power
  on, needed for Optiboot which skips the bootloader after a power cycle
- Clear errors for outdated ESP32 firmware and missing wires
- README: bootloader table, reset wire, troubleshooting

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-13 00:44:35 +02:00
1 parent bea5b9daa1
commit cefb76c1ad
8 files changed
+220 -77

No files matched your search

+6 -1
View File
@@ -50,6 +50,9 @@ const int relay = 13; // Pin of the relay, powers the panels and the Nano (LOW =
HardwareSerial &nanoSerial = Serial2;
const int nano_rx = 16; // UART2 RX, wired to the Nano TX (through a 5V -> 3.3V divider)
const int nano_tx = 17; // UART2 TX, wired to the Nano RX
// Optional: GPIO that resets the Nano through an NPN transistor (HIGH = reset), -1 = not wired.
// Needed to flash a Nano with the Optiboot bootloader, which doesn't start after a power cycle.
const int nano_reset = -1;
bool relay_on = false;
// Max wait for the Nano's READY after power on (also the wait when Nano TX is not wired to our RX)
const uint32_t NANO_BOOT_TIMEOUT = 3000;
@@ -57,8 +60,10 @@ const uint32_t NANO_BAUD = 115200;
// Flashing the Nano over WiFi (see README.md, pio run -e nano_ota -t upload)
const uint16_t NANO_FLASH_PORT = 2323;
const uint32_t NANO_BOOTLOADER_BAUD = 57600; // old Nano bootloader
// The bootloader baud rate is chosen by the uploader (57600 old bootloader, 115200 Optiboot)
const uint32_t NANO_POWER_OFF_TIME = 1000; // relay off time to make sure the Nano resets
const uint32_t NANO_RESET_DELAY = 200; // after power on, before the reset pulse
const uint32_t NANO_RESET_PULSE = 50;
int hour = 0;
int minute = 0;
+20 -4
View File
@@ -60,18 +60,29 @@ void relayOff()
pinMode(nano_tx, INPUT);
}
// Flash session started: power cycle the Nano so its bootloader runs, and talk to the
// Flash session started: restart the Nano so its bootloader runs, and talk to the
// bootloader at its baud rate. The PC uploader keeps sending sync bytes until it answers.
void startNanoFlash()
// The old bootloader also starts after a power cycle; Optiboot only after a reset pulse.
void startNanoFlash(uint32_t baud)
{
if (relay_on)
relayOff();
delay(NANO_POWER_OFF_TIME);
nanoSerial.begin(NANO_BOOTLOADER_BAUD, SERIAL_8N1, nano_rx, nano_tx);
nanoSerial.begin(baud, SERIAL_8N1, nano_rx, nano_tx);
digitalWrite(relay, LOW);
if (nano_reset >= 0)
{
delay(NANO_RESET_DELAY);
digitalWrite(nano_reset, HIGH);
delay(NANO_RESET_PULSE);
digitalWrite(nano_reset, LOW);
}
// drop anything the Nano sent before its bootloader started
while (nanoSerial.available())
nanoSerial.read();
digitalWrite(relay, LOW);
}
// Flash session ended: switch off, the clock powers the Nano on again with the new firmware
@@ -312,6 +323,11 @@ void setup()
otaPanicMode = false;
pinMode(relay, OUTPUT);
if (nano_reset >= 0)
{
digitalWrite(nano_reset, LOW);
pinMode(nano_reset, OUTPUT);
}
relayOn();
display.begin(&nanoSerial);
+39 -2
View File
@@ -1,6 +1,6 @@
#include "nano_flash_bridge.h"
NanoFlashBridge::NanoFlashBridge(uint16_t port, HardwareSerial &serial, void (*onStart)(), void (*onEnd)())
NanoFlashBridge::NanoFlashBridge(uint16_t port, HardwareSerial &serial, void (*onStart)(uint32_t baud), void (*onEnd)())
: _server(port), _serial(serial), _onStart(onStart), _onEnd(onEnd)
{
}
@@ -19,10 +19,19 @@ bool NanoFlashBridge::handle()
if (!client)
return false;
uint32_t baud = _readHeader(client);
if (!baud)
{
client.print("ERR expected FLASH <baud>\n");
client.stop();
return false;
}
_client = client;
_client.setNoDelay(true);
_active = true;
_onStart();
_onStart(baud);
_client.print("OK\n");
_last_activity = millis();
}
@@ -58,6 +67,34 @@ bool NanoFlashBridge::handle()
return true;
}
// Read the "FLASH <baud>" line. Returns the baud rate, or 0 when it is missing or invalid.
uint32_t NanoFlashBridge::_readHeader(WiFiClient &client)
{
char line[32];
uint8_t len = 0;
uint32_t start = millis();
while (millis() - start < HEADER_TIMEOUT && client.connected())
{
if (!client.available())
{
delay(1);
continue;
}
char c = client.read();
if (c == '\n')
{
line[len] = '\0';
unsigned long baud;
if (sscanf(line, "FLASH %lu", &baud) == 1 && baud >= 9600 && baud <= 1000000)
return baud;
return 0;
}
if (c != '\r' && len < sizeof(line) - 1)
line[len++] = c;
}
return 0;
}
void NanoFlashBridge::_end()
{
_client.stop();
+10 -5
View File
@@ -5,14 +5,16 @@
// WiFi to serial bridge for flashing the Nano from the PC (pio run -e nano_ota -t upload).
//
// When a TCP client connects, onStart() is called to restart the Nano into its bootloader.
// After that, bytes are copied between the TCP connection and the Nano's serial port
// The uploader (tools/nano_net_upload.py) connects and first sends one line:
// FLASH <baud>\n
// with the baud rate of the Nano's bootloader (57600 old bootloader, 115200 Optiboot).
// onStart(baud) then restarts the Nano into its bootloader, the bridge answers "OK\n",
// and from then on bytes are copied between the TCP connection and the Nano's serial port
// until the client disconnects or is idle too long. onEnd() is then called.
// The uploader on the PC side is tools/nano_net_upload.py.
class NanoFlashBridge
{
public:
NanoFlashBridge(uint16_t port, HardwareSerial &serial, void (*onStart)(), void (*onEnd)());
NanoFlashBridge(uint16_t port, HardwareSerial &serial, void (*onStart)(uint32_t baud), void (*onEnd)());
void begin();
@@ -21,16 +23,19 @@ public:
bool handle();
private:
// Max wait for the FLASH line after connecting
static const uint32_t HEADER_TIMEOUT = 2000;
// Give up when no data moves in either direction for this long
static const uint32_t IDLE_TIMEOUT = 10000;
WiFiServer _server;
WiFiClient _client;
HardwareSerial &_serial;
void (*_onStart)();
void (*_onStart)(uint32_t baud);
void (*_onEnd)();
bool _active = false;
uint32_t _last_activity = 0;
uint32_t _readHeader(WiFiClient &client);
void _end();
};