Overanalyzing a minor quirk of Espressif’s reset circuit

The mystery

In the previous article, I briefly mentioned a slight difference between the ESP-Prog and the reproduced circuit, when it comes to EN:

Breadboard circuit with pull-up resistors and delay capacitor, no board connected, EN and IO0 only
ESP-Prog connected to an ESP32-S2-Saola-1RI board, EN and IO0 only

Focusing on EN, it looks like the voltage level goes back to 3.3V much faster on the ESP-Prog than on the breadboard circuit. The grid is horizontally spaced at 2ms, so it takes about 0.8ms for the breadboard circuit to cross 2V, and about 0.2ms for the ESP-Prog.

Zooming in

I actually noticed this when observing the behavior of the breadboard circuit with my oscilloscope when trigering a reboot into bootloader mode. This uses the same C program as the one at the end of the previous article, except that there is no call to usleep():

#include <stdio.h> // puts()
#include <fcntl.h> // open(), O_RDWR, O_NOCTTY
#include <sys/ioctl.h> // ioctl(), TIOCMSET, TIOCM_RTS, TIOCM_DTR
#include <stdlib.h> // exit()

void tiocmset(int fd, int mask) {
    if (ioctl(fd, TIOCMSET, &mask) < 0) {
        puts("Failed to set mode");
        exit(1);
    }
}

int main(void) {
    int fd = open("/dev/ttyUSB1", O_RDWR | O_NOCTTY);
    if (fd < 0) {
        puts("Failed to open device");
        return 1;
    }
    sleep(1);

    while (1) {
        // REBOOT IN BOOTLOADER MODE
        // assert RTS = turn off
        tiocmset(fd, TIOCM_RTS);
        // assert DTR and clear RTS = start into bootloader mode
        tiocmset(fd, TIOCM_DTR);
        // clear = no effect
        tiocmset(fd, 0);

        sleep(1);
    }
}

Without usleep(), there is still about 2ms between calls to ioctl(). This is due to the polling rate of USB Full Speed being 1ms, and possibly the driver having to wait for acknowledgement. In any case, we can observe something very new:

Breadboard circuit when rebooting in bootloader mode without pausing

Here, we can see that, when DTR goes electrically low and RTS electrically high, the electrical level of EN starts rising slowly. In fact, when I waited longer before reverting DTR to 3.3V as well, I was able to confirm that it took about 10ms for EN to reach 2.1V, just what we would have expected initially!

However, when DTR goes back to 3.3V, EN suddenly starts rising much faster. I confirmed that this was totally unrelated to the electrical level of RTS:

Although toggling RTS has an effect on IO0, it does nothing on the behavior of EN in this case.

It turns out that my mental model for how transistor works was inaccurate.

A transistor against the current

To understand this, let’s look at what happens in the logic circuit in each case. We’ll be looking specifically at the transistor that controls EN. For this, we’ll use the simulation below, and look at the current right after each event of this series:

  1. Flip the bottom switch
  2. Flip the top switch
  3. Flip the bottom switch and the top switch

For quick reference, here are the schematics of an NPN transistor:

Schematics of an NPN transistor, with collector (C), base (B) and emitter (E) pins (source)

First, we flip the RTS switch. This results in DTR having a higher electrical potential than RTS, and thus some current flowing from the base (B) to the emitter (E). The transistor itself lowers the potential by about 0.6V, so the current coming from B is about (3.3V – 0.6V) / 10kΩ = 270µA.

Since the potential on the base is high, the transistor is in “saturation mode”, meaning that it draws current from the collector (C) to the emitter (E). The amount of current from C to E is proportional to the current from B to E, typically with a ratio of 100. Thus, the transistor draws about 27 mA from C. Since there is a resistor between C and the 3.3V rail, but no resistor between C and the capacitor, most of the current is drawn from the capacitor.

After a very short time (~400 µs), the capacitor gets completely discharged. The transistor draws all the current that can come from the 3.3V rail (3.3V / 10kΩ = 330µA), so EN stays at a 0V potential.

Now, when we flip the DTR switch, E and B are both at 0V, so the transistor effectively “cuts off”. The current from the 3.3V is not drawn by the transistor and instead goes to the capacitor, slowly charging it:

This corresponds to the slow rise of EN, with a characteristic time τ = 10kΩ × 1μF = 10ms. If we flip RTS, the transistor is still in cut-off mode, so this changes nothing.

However, if we also flip DTR, B is now at a higher electrical level than E, putting the transistor into “reverse active” mode. Although this is not the typical way that transistors are used, this is a real phenomenon. As a result, the transistor draws current from E to C!

With DTR at 3.3V and EN at 0V, and considering the ~0.6V voltage drop from the transistor, the base would again draw about 270 µA. However, the capacitor already partially charged from the earlier situation, so the voltage difference between B and C is lower than 3.3V. As a result, we observe a lower current on B (which keeps decreasing).

The gain in reverse active mode is typically lower than in forward active mode. Since this is rarely used, anyway, simulations might not be accurate. To reproduce the observed behavior in CircuitJS, I had to use a gain of 10 instead of the default transistor model with a gain of 1.

In any case, this results in about an additional 2.5mA charging the capacitor, or an almost 10× faster charge! This is exactly what we observe:

Flipping RTS quickly brings EN from 3.3V to 0V. Then, flipping DTR lets EN slowly charge over τ=10ms. But, when flipping DTR and RTS again, EN suddently rises much faster, at τ=~1ms.

This entirely explains the behavior of EN in the breadboard circuit.

Double charged

However, this still does not explain the difference with the ESP-Prog! As a reminder, this is what EN looks like in the breadboard circuit:

Breadboard circuit when rebooting in bootloader mode without pausing

And here in the ESP-Prog + Saola:

ESP-Prog when rebooting in bootloader mode without pausing

From the above screenshot, you could think that we no longer have a difference in charging speed between DTR low and DTR high. However, we can still find it if we expand EN:

Although EN charges much faster, we still see a similar pattern, where toggling DTR to a high electrical level makes the charging (even) faster.

By tracking back on the reasoning I followed to build the breadboard circuit, I realized that I made an incorrect assumption. Specifically, when looking at the components connected to CHIP_PU on the Saola board, I assumed that the Saola’s own UART-to-USB adapter would not be driving DTR/RTS, and thus do nothing to CHIP_PU.

The reset circuit in the ESP32-S2-Saola-1RI. CHIP_PU is connected to it through a 0Ω resistor.

To challenge this assumption, I removed the red wire that I used to power the Saola board from the ESP-Prog board, and instead connected it with a USB micro-B cable. I also delayed toggling DTR back to 3.3V, so that I could better see the initial charge pattern of EN.

Doing nothing else, I see the same fast-charge pattern:

Cursor A (solid vertical line) is positioned at the instant when DTR flips and EN starts charging. Cursor B (dotted vertical line) is placed 10ms later. At this instant, EN is already at 3.14V instead of the expected 2.1V.

Then, I just toggled DTR and RTS on the Saola’s UART-to-USB adapter by simply opening the corresponding device with tio (Linux always toggles DTR & RTS), thus cutting off this second reset mechanism. And, lo and behold, EN now charges slower!

With the cursors positioned just as before, we see that EN only reaches 2.32V after 10ms. This is still slightly larger than the expected 2.1V, but much closer.

If we look at all channels again, it now looks almost identical to the breadboard circuit.

Except…

Just one more thing

Although EN now looks like we expect, IO0 also changed! Let’s look again at all cases:

ESP-Prog when opening the serial device
ESP-Prog when restarting normally
ESP-Prog when restarting in bootloader mode
ESP-Prog when using the test pattern used in Reproducing Espressif’s reset circuit

What we observe is that, when EN becomes electrically low, IO0 slowly discharge, regardless of DTR/RTS. Note that the reverse is not true: IO0 being low does not force EN low. This is different from what we observed in Reproducing Espressif’s reset circuit.

My understanding is that, when we turn the ESP32-S2 chip, we also turn off the voltage regulator that supplies the 3.3V rail used by the reset circuit (with the pull-up resistor), letting a capacitor connected to IO0 slowly discharge.

This is why you might want to desolder R28 and R29, the two 0Ω resistors: this completely disables the Saola’s automatic reset circuit, stopping it from interfering with the ESP-Prog. And this is exactly why R28 and R29 exist.

The end?

This concludes my investigations of Espressif’s reset circuit, but not of UART! There are still a couple of things I do not completely understand and want to uncover before writing the promised “main” article on UART.

One thought on “Overanalyzing a minor quirk of Espressif’s reset circuit

Leave a Reply

Your email address will not be published. Required fields are marked *