ESP32: UART Bridge

Overview

This tutorial shows an example of how to send UART data over the network using an ESP32. A real bridge can be built using two devices, though in this tutorial we'll only cover one side of it.

Since UART0 in the ESP32 is used for flashing, and logging, in order to interface to one of the other UARTs in the ESP32, we need to connect to two pins. In this example we'll be using UART1 in GPIOs 25 and 26, and we'll be using a USB-to-UART interface board so we can access that from our computer.

Since USB is a 5V interface, many of these boards have a VIO or VCCIO pin. This pin is the voltage supply for the I/O section of the interface chip (like those of the FT232 family, for example), and as the ESP32 in our module is working at 3.3V, we'll connect this (3V3 pin in many ESP32 modules) to our interface board too.

NOTE: If your board doesn't have a VIO pin, make sure it outputs 3.3V. In this case, it is good practice to put resistors in series with the TD and RD pins in order to limit any currents flowing due to the power supply voltage difference between this USB-to-UART board and the ESP32 module.

interface

PAY ATTENTION: Make sure your interface board does NOT output RS-232 levels, otherwise you'll burn the ESP32 GPIOs.

This tutorial is a hardware example following on the UART bridge tutorial. You should read and follow that tutorial first.

Check the ESP32 device dashboard tutorial for a bit of extra information on the typical ESP-IDF project structure.

Build and try

  • If you've not already done so, clone the Mongoose Library repo

    $ git clone https://github.com/cesanta/mongoose
    
  • This example requires you have Docker installed and your user is able to run it. If in doubt, check

    $ docker ps
    
  • Build the example. Unless you already have an ESP-IDF image, this will download it, what will take some time. The first build process also takes a while.

    $ cd mongoose/examples/esp32/uart-bridge
    $ make build
    ...
    Successfully created esp32 image.
    Generated [...]/mongoose/examples/esp32/uart-bridge/build/mongoose-esp32-example.bin
    ...
    Project build complete. To flash, run this command:
    ...
    
  • Now let's flash it. We need to know the device name for the serial port that was assigned to the ESP32. If it is the only USB serial port we have, it will usually be /dev/ttyUSB0. Plug your ESP32 board first, wait a couple of seconds and then plug your USB-to-UART interface board. If you have esptool.py installed, you can run the command shown in the former action; here, we will use make:

    $ make flash PORT=/dev/ttyUSB0
    ...  
    Hash of data verified.
    
    Leaving...
    Hard resetting via RTS pin...
     ESP32
    Running ninja in directory /home/scaprile/work/cesanta/mongoose/examples/esp32/uart-bridge/build
    Executing "ninja flash"...
    Done
    
  • Now let's connect to the ESP32 board, in this example we'll be using minicom; make sure you configure it for 115200bps. You can also download and unzip esputil, in that case you can run make flash2 PORT=/dev/ttyUSB0 in the prior step or esputil -p /dev/ttyUSB0 monitor now. Let's reset our board and see how it starts:

    $ minicom -D /dev/ttyUSB0
    ...
    I (477) cpu_start: Application information:
    I (482) cpu_start: Project name:     mongoose-esp32-example
    I (488) cpu_start: App version:      1
    ...
    I (572) cpu_start: Starting scheduler on PRO CPU.
    I (0) cpu_start: Starting scheduler on APP CPU.
    2a 2 main.c:21:app_main                 FS at /spiffs initialised, status: 0
    2c 2 main.c:34:app_main                 WiFi is not configured, running CLI. Press enter
    
  • The first time we flash it, there won't be a WiFi configuration, so we'll use a small CLI built into the firmware to configure it. As the CLI has no echo, we can configure our serial port software for local echo; in minicom is <CTRL-A> E. Now press the enter key as instructed:

    --- CLI command: ''
    --- Unknown command. Usage:
    ---   set NAME VALUE
    ---   rm FILENAME
    ---   cat FILENAME
    ---   ls
    ---   reboot
    ---   wifi WIFI_NET WIFI_PASS
    --- CLI output end
    
  • We can now enter our WiFi SSID and password

    wifi mynet mypass
    
    c9ea 2 wifi.c:45:event_handler          IP ADDRESS: 192.168.69.231. Go to:      
    c9ee 2 wifi.c:46:event_handler          http://192.168.69.231                   
    c9f4 2 wifi.c:93:wifi_init              connected to ap SSID:mynet
    ca03 2 cli.c:7:cli_wifi                 Reboot now                              
    --- CLI output end                                                              
    
  • Let's reset our board again and see how it starts:

    ...
    I (1030) wifi:connected with mynet, aid = 2, channel 11, BW20, bssid = 96:f6:52:a4:c0:1e
    I (1030) wifi:security: WPA2-PSK, phy: bgn, rssi: -56
    I (1030) wifi:pm start, type: 1
    ...
    I (2660) esp_netif_handlers: sta ip: 192.168.69.231, mask: 255.255.255.0, gw: 192.168.69.1
    817 2 wifi.c:45:event_handler           IP ADDRESS: 192.168.69.231. Go to:
    81c 2 wifi.c:46:event_handler           http://192.168.69.231
    821 2 wifi.c:93:wifi_init               connected to ap SSID:mynet
    827 2 main.c:46:app_main                Mongoose v7.7 on http://0.0.0.0:80
    832 2 net.c:151:config_apply            Applying config: {"tcp":{"url":"tcp://0.0.0.0:4001","enable":true},"ws":{"url":"ws://0.0.0.0:4002","enable":true},"mqtt":  {"url":"mqtt://broker.hivemq.com:1883?tx=b/tx&rx=}
    847 2 uart.c:22:uart_open               1: 25/26/115200, 0 0 0
    84a 3 net.c:206:mg_listen               1 0x36 http://0.0.0.0:80
    878 3 net.c:206:mg_listen               2 0x37 tcp://0.0.0.0:4001
    87b 3 net.c:206:mg_listen               3 0x38 ws://0.0.0.0:4002
    
  • Now start your browser and point it to the IP address reported, in our case is http://192.168.69.231 as can be seen above, yours will depend on your network and how your Access Point DHCP server is configured. The main page is loaded; the JavaScript code on the web page GETs the configuration from /api/config/get, renders the UI, and connects to the WebSocket server (if enabled). The default configuration has random pin numbers for the serial I/O, let's put the pins we are using, in our case 26 and 25 as seen on the picture.

browser image
  • Let's reset our board one last time and see how it starts. You should see the same messages as before, but if you enabled MQTT you'll see some extra messages as the board resolves the broker address and tries to connect to it.

  • Now open a second terminal on which to run another copy of a serial port software (we use minicom; make sure you configure it for 115200bps or the speed you may have set), this time to connect to the USB-to-UART board and through it to the ESP32. Check your board's device name.

    $ minicom -D /dev/ttyUSB1
    
  • Type anything on this terminal window, you'll see that rendered on the UI.

  • Conversely, write something on the UI as instructed, and you'll see it at the terminal window. The first terminal will be showing these actions on the log.

  • From here on, please go to the UART bridge tutorial and repeat some of the steps depicted there

    • You can manually connect to the WebSocket server with a WebSocket client like for example wscat
      $ wscat --connect localhost:4002
      Connected (press CTRL+C to quit)
      < 
      
    • You can connect to the TCP server with a client like for example nc
      $ nc localhost 4001 
      
    • If you enable the MQTT client, you can send a message to the device via MQTT using any MQTT client, for example mosquitto_pub, by publishing to the configured topic:
      $ mosquitto_pub -h broker.hivemq.com -t "b/rx" -m "hello"
      
    • With any MQTT client, you can subscribe to the configured topic and receive what enters the UART:
      $ mosquitto_sub -h broker.hivemq.com -t "b/tx"
      

How it works

Here we'll cover those aspects that are specific to the ESP implementation, for the details on the application and the UI, please see the UART bridge tutorial.

UART

The HAL uses a set of functions to read, write, and initialize the UART:

In order to include the actual UART implementation, we need to define the UART_API_IMPLEMENTED macro, as can be seen on the common code

We do that at the component level CMake file, main/CMakeLists.txt:

The driver uses generic code, we also need to configure which UART we'll be using; we do that in main.h:

The driver itself is mainly a wrapper to the ESP-IDF UART API:

Configuration

The HAL uses a set of functions to read and write the configuration to a non-volatile medium:

In order to include our actual files, we need to define the UART_API_IMPLEMENTED macro, as we've seen above. The configuration will be stored in a filesystem on the device.

The driver itself is mainly a wrapper to the Mongoose filesystem API. The ESP-IDF provides a POSIX filesystem support, so we use that. The macro FS_ROOT is defined in main.h and points to the SPIFFS partition: /spiffs

main

We mount the SPIFFS partition and try to read the WiFi credentials file. Since it is in JSON format, we parse its contents using Mongoose's JSON API and initialize WiFi, to finally initialize the event manager and call it in an infinite loop.

Otherwise, we run the CLI, passing it whatever chars the user enters on the flashing/debugging serial port. The CLI itself is very simple, it buffers characters until the user hits the enter key, then compares to a set of strings to perform simple tasks. The most important is of course wifi that allows us to enter proper credentials for our WiFi network, initializing WiFi and saving them to the WiFi configuration file:

Here, the %m specifier for mg_file_printf() calls mg_print_esc() that simplifies printing JSON strings.

As we'll also be using the packed filesystem for the web server code, we need to set the MG_ENABLE_PACKED_FS macro so Mongoose includes the proper code; we do that at the component level CMake file, main/CMakeLists.txt:

Browse latest code