Serial Reset

Task of the day is get a JavaScript program running in a browser to send messages to a serial port. And maybe even reset an ESP device using the hardware handshake lines. Then I can start having a conversation with a device. This website tells me how to make a serial connection from a browser:

https://web.dev/serial/

I’m going to make a little SerialManager class to manage the connection to the serial port.

Connecting to a serial port

Let’s have a look at the function that makes the connection to the device:

async connectToSerialPort() {
  // Prompt user to select any serial port.

  if (!"serial" in navigator) {
    this.port = null;
    return { worked: false, message: "This browser doesn't support serial connection. Try Edge or Chrome." };
  }

  try {
    this.port = await navigator.serial.requestPort();
    await this.port.open({ baudRate: 115200, bufferSize: 10000 });
  }
  catch (error) {
    return { worked: false, message: `Serial port open failed:${error.message}` };
  }

  return { worked: true, message: "Connected OK" };
}

When the function runs it pops up a dialog asking the user to select a serial device. Then it creates a connection. Note that I'm using a funky error return mechanism where the function returns whether or not it worked along with a message. I can use this to get a serial connection and it seems to work.

Resetting the ESP device

Next we need to reset the ESP device into upload mode. This seems to involve some fancy footwork with the hardware handshake lines. You can find the code in the esptoo.py file at line 564:

self._setDTR(False)  # IO0=HIGH
self._setRTS(True)   # EN=LOW, chip in reset
time.sleep(0.1)
if esp32r0_delay:
    # Some chips are more likely to trigger the esp32r0
    # watchdog reset silicon bug if they're held with EN=LOW
    # for a longer period
    time.sleep(1.2)
self._setDTR(True)   # IO0=LOW
self._setRTS(False)  # EN=HIGH, chip out of reset
if esp32r0_delay:
    # Sleep longer after reset.
    # This workaround only works on revision 0 ESP32 chips,
    # it exploits a silicon bug spurious watchdog reset.
    time.sleep(0.4)  # allow watchdog reset to occur
time.sleep(0.05)
self._setDTR(False)  # IO0=HIGH, done

It uses the DTR and RTS lines (which are normally used for handshaking the serial connection) to reset the chip. Not all ESP devices have these connected, but the Wemos devices that I want to specifically target with my program do. You can kind of see what they do:

  1. drop DTR
  2. raise RTS
  3. wait a while
  4. raise DTR
  5. drop RTS
  6. drop DTR

This combination of changes means "please start the chip in bootloader mode". Here's my JavaScript interpretation:

async resetIntoBootloader() {
  console.log("Resetting into the Bootloader");
  await this.port.setSignals({ dataTerminalReady: false });
  await this.port.setSignals({ requestToSend: true });
  await this.delay(100);
  await this.port.setSignals({ dataTerminalReady: true });
  await this.port.setSignals({ requestToSend: false });
  await this.delay(50);
  await this.port.setSignals({ dataTerminalReady: false });
  console.log("Reset into bootloader");
}

This does exactly the same sequence. It uses a little function called delay that I've made that will pause the program for a while.

async delay(timeInMs) {
  return new Promise(async (kept, broken) => {
    setTimeout(async () => {
      return kept("tick");
    }, timeInMs);
  });
}

Note that the JavaScript does a lot of awaiting. I haven't really got time to go into this in detail, suffice it to say that the allows my code to stop for a while without the webpage that it is part of getting stuck. Anyhoo, it seems to work, in that the ESP device stops when I hit it with the reset sequence. Tomorrow I'm going to try sending some commands.