Espressif’s Automatic Reset

In previous articles, we saw how to use “real” UART, and looked into the trick used by Arduino to automatically reset boards when uploading firmware. Today, we’ll look into how Espressif does something similar, using even more tricks.

“Real” UART on the Saola

As usual, let’s first simply connect the UART adapter. Again, we connect GND to GND, TX to RX and RX to TX. I also choose to power the board using the VCC pin of the UART adapter, but you can power it from USB instead.

The labels on the FT232RL board can be hard to see, so the above diagram is handy (source)
Using a black wire for ground (GND) and a red one for power (5V here) is a good habit to have.

Note: There is no widespread convention for the wires between TX and RX. However, choosing one makes it simpler to connect wires without having to check which is which every five seconds. Lately, I have taken the habit of using orange for TX on the adapter (and RX on the target), and yellow for RX on the adapter (and TX on the target).

In any case, when we plug the UART adapter with an USB cable, the target device (ESP32-S2-Saola-1RI here) should start running.

Just like in my first article on UART, the pre-loaded firmware on the board makes the RGB LED take red, green and blue colors in turn.

In a serial client, we can again see:

Now, for the new part!

Automatic Reset with “real” UART

In the Arduino article, we exploited the fact that DTR and RTS pins change from being electrically high to electrically low when a program opens the associated serial device. Let’s do something similar and connect the DTR pin of the UART adapter to the RST pin of the Espressif board.

I use a green wire for reset. But, again, that’s just a personal convention.

Note that I did not put a capacitor between DTR and RST. This is intentional. It does mean that, if we just open the serial device (with, e.g., tio), the target board stops running. But we’ll address that a different way: we are going to control the level of these pins manually1.

First, we can check the status of these pins in tio, by hitting Ctrl+t, and then uppercase L. tio should produce an output like the one below.

We can see that both DTR and RTS are electrically low, as expected. We can switch the value of DTR by hitting Ctrl+t, and then lowercase g:

By controlling the level of DTR, we can reset the target board from software!

Bootloader mode

In the case of Arduino, to upload a new firmware to the microcontroller, you just need to reset it and send commands quickly after. This has the merit of being simple, but this means:

  • There is a delay when the microcontroller starts, where it checks for any commands to upload firmware. This can be unwanted for a final product.
  • The software used to upload the firmware (AVRDUDE) must have the right timing with the reset. This can make it harder to handle in some cases. For instance, when resetting manually.

Espressif went a different way when it comes to firmware upload. Instead of having a sequence where the bootloader runs, then the user code runs, Espressif chips will boot in either mode. Selecting the boot mode is done by controlling the electrical level of the GPIO0 pin.

Extract from the documentation. The sentence about the “pullup resistor” says that, if we do not connect anything to GPIO0, it will be electrically high by default. The last sentence explains how to manually set GPIO0 to a low electrical level by pressing the BOOT button.

Let’s use the UART’s RTS to control the electrical level of GPIO0. For this, we need an additional cable.

Here, I have added a white wire between RTS on the UART adapter and GPIO0 (simply labelled 0) on the ESP32-S2-Saola-1RI board. Since there are no pin headers on the RTS pin, I used a test probe that I got for 3 € off AliExpress.

First, let’s check that we can still boot it in normal mode. For this, we must ensure that GPIO0 is at a high electrical level. Since RTS is automatically set to a low electrical level when we open the serial device with tio, we must toggle it first.

So far, so good! But nothing new. What happens if we reset the target board while GPIO0 is held at a low electrical level? We’ll toggle RTS to control GPIO0, and use DTR to reset the board.

When we reset the board with GPIO0 (RTS here) in a low electrical state, we get completely different messages on the serial connection. The line “waiting for download” indicates that the bootloader is waiting for commands to receive a new firmware.

We now have full control on the boot mode of the card. We just need to look at one last trick, and the easiest way is to look at the built-in USB adapter.

Automatic reset over USB

Espressif development boards come with their own USB-to-UART adapter. This works exactly as if the UART adapter we used so far (except it is a CP2102 where I used a FT232RL).

Note the “USB-to-UART Bridge” chip between the USB port and the ESP32-S2 module.

We do need to check how it is connected to the ESP32-S2 chip, however. For this, we need to take a look at the schematics of the board. Let’s start from the CP2102 chip:

There are many things there, but we only need to focus on what we are interested in. Here:

  • GND is connected to the ground, as expected;
  • TXD to an internal label U0TXD;
  • RXD to internal label U0RXD;
  • DTR to internal label DTR;
  • RTS to internal label RTS.

Let’s look in the rest of the schematics what these internal label connect to.

As expected, we find U0TXD and U0RXD directly connected to the ESP32-S2 chip’s2 RXD0 and TXD0 pins. We find DTR and RTS in another part of the schematics:

DTR and RTS are connected to EN_Auto and IO0_Auto, which are just other internal labels for the board. These are connected through resistors3 to the pins CHIP_PU (“Power-Up”) and GPIO0 of the microcontroller.4

DTR and RTS are not directly connected to the chip. They are cross-connected to two transistors first.

The table below this part of the schematics indicates the behavior of this circuit as a logical gate. However, I find it mostly confusing. Considering DTR, RTS, EN and IO0 are all “active low”, it should not matter whether “0” (respectively “1”) mean electrical low or logical low, but it makes it harder to reason about. To avoid any ambiguity, I have made it fully explicit in the table below.

AssertedDTRRTSENIO0Function
Neither3.3 V3.3 V3.3 V3.3 VNormal run
RTS & DTR0 V0 V3.3 V3.3 VNormal run
RTS3.3 V0 V0 V3.3 VOff
DTR0 V3.3 V3.3 V0 VBootloader

If we look at the right column, we can see that we can still reset the chip and start it in normal mode or bootloader mode by selecting the right electrical levels for DTR and RTS. What changed is that the chip will run whether they are both high or both low. This tells us the purpose of this circuit: avoiding the automatic assertion of DTR and RTS from turning the chip off.

When we connected DTR and RTS directly to RST and GPIO0, opening the serial device would turn the chip off. We could turn it on again by de-asserting DTR (setting it to electrically high), but a reset was unavoidable.

With the two transistors in between, the chip will keep running even when we open the serial device. We can then choose to turn it off by de-asserting DTR. After that, we can start it again in normal mode or in bootloader mode as we want.

This is explained in the “Automatic Bootloader” section of the documentation.

Understanding the logical gate

To understand how these transistors achieve this, we can play around with the CircuitJS simulation below.

Both transistors in the circuit are “NPN transistors”:

They have three pins:

  • B: the base, in the middle
  • E: the emitter, on the side of the arrow
  • C: the collector, on the other side

Simplifying slightly, when B is at higher electrical level than E (by 0.7V), E and C get connected together. When this happen, the transistor is said to be “saturated”.

Note: For a PNP transistor, the connection would happen when B is at a lower electrical level than E instead.

Looking at the top transistor (Q1, connected to EN), when DTR is electrically high and RTS is electrically low, B is electrically high and E is electrically low, so the transistor is saturated, connecting C to E. This causes C, and thus EN, to also be electrically low.

If we switch DTR to low and RTS to high, B is low and E is high, so E and C are not connected. To avoid leaving the electrical level of C (and thus EN) “floating”, it is connected to a resistor to 3.3V, making it default to electrically high. This is called a “pull-up” (if it were connected to 0V instead, it would be called a “pull-down”).

The opposite happens in the bottom transistor (Q2, connected to IO0).

We can also look at what happens if we flip the switches in the simulation.

First, let’s set DTR and RTS electrically low by flipping both switches to connect them to the ground. If you want to reproduce this, pause the simulation while you flip the switches.

This does not change the state of the output (the electrical level of EN and IO0). This is the main purpose of this circuit: opening the serial device does not change the state of EN, avoiding a forced reset.

Now, say we want to turn the board off. We switch DTR to electrically high:

The electrical level of EN flips to low, causing the board to turn off. IO0 is still electrically high, but this does matter since the board is off. If we flip DTR again, we go back to the previous situation, causing the board to start normally.

If we want to start in in bootloader mode, we flip both DTR and RTS, at the same time:

If we want to start in bootloader mode instead, we toggle DTR and RTS at the same time. By having DTR electrically low and RTS electrically high, EN becomes electrically high and IO0 electrically low.

This causes EN and IO0 to both flip as well. Since EN is now electrically high, the board turns on. Since IO0 is electrically low, it starts in bootloader mode.

Conclusion

The reset mechanism of Arduino is pretty simple, but causes the board to reset whether the developer wants it or not. The mechanism used by Espressif is significantly harder to understand fully, but does provide a much nicer behavior for developers with full control from the host computer.

  1. Like we did in the article about DTR & RTS on Linux. ↩︎
  2. Strictly speaking, they are connected to the module. But you can check the schematics of the module itself (page 21 of the datasheet) and confirm that the TX/RX pins of the module are connected to those of the ESP32-S2 chip. ↩︎
  3. The resistors are 0Ω ones. This indicates that the goal is not to use an actual resistor, but to give an opportunity to users to easily modify the board. Here, this gives the option to disable the automatic reset behavior, if it is not wanted. For instance, if keeping IO0 electrically high or low interferes with some desired features. ↩︎
  4. Strictly speaking, they are connected to the module, here a ESP32-S2-WROVER-I. Inside the module, these pins are mapped to the corresponding pins of the actual ESP32-S2 microcontroller. Also note that Espressif’s documentation sometimes uses “EN”, for “ENable”, instead of “CHIP_PU”. ↩︎

Leave a Reply

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