ESP-IDF firmware that turns an ESP32 into a WiFi-to-PPP gateway: it joins a WiFi network as a station and offers that connection to another MCU as a standard PPP server over UART, NAT'ing the MCU's traffic onto the WiFi link.
This can be used to provide WiFi capabilities to the MiSTle FPGA Companion via USB through a ESP32.
Built on Espressif's eppp_link
component, configured for a real PPP netif (CONFIG_EPPP_LINK_USES_PPP=y)
rather than its lighter proprietary framing, so the peer side can be any
standard PPP client (lwIP ppp, Linux pppd, etc.) — not necessarily
another eppp_link device.
- ESP-IDF >= 5.2
- An ESP32 (or other WiFi-capable ESP32-*) target
If you don't already have ESP-IDF >= 5.2 installed, follow Espressif's Get Started guide, or in short:
git clone -b v5.4.1 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32
Each new shell needs the IDF environment loaded before using idf.py:
. $HOME/esp-idf/export.sh
From this project's directory:
idf.py set-target esp32
idf.py menuconfig # set WiFi SSID/password under "esp_ppp Configuration"
The component manager fetches eppp_link (and its dependencies) into
managed_components/ automatically the first time you build; no manual
step is required.
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
Replace /dev/ttyUSB0 with the ESP32's serial port (omit -p to let
idf.py auto-detect it). On Linux you may need to add your user to the
dialout group (or uucp on some distros) to access the port without
sudo:
sudo usermod -aG dialout $USER # then log out/in for it to take effect
Use Ctrl+] to exit the serial monitor.
Connect the ESP32's PPP UART to the peer MCU (cross TX/RX, common ground):
| ESP32 | Peer MCU |
|---|---|
| TX (default GPIO1) | RX |
| RX (default GPIO3) | TX |
| GND | GND |
Pins, UART port and baudrate are configurable under
idf.py menuconfig -> esp_ppp Configuration. Default baudrate is 115200;
lower it if the wiring/link is unreliable.
The default UART port (0) is the same UART used for the console/log output
and for flashing, i.e. the board's built-in USB-serial adapter. That's
convenient because it means no extra wiring is needed to talk PPP to the
ESP32 (see Testing with a Linux PC below), but
it also means log output and PPP framing will collide on the wire unless
you either move the console to a different UART (idf.py menuconfig ->
Component config -> ESP System Settings -> Channel for console output) or
move the PPP link to a different UART/pins instead.
The WiFi SSID/password set via idf.py menuconfig are only compiled-in
defaults ("changeme" / "changeme" out of the box). At runtime the
firmware actually manages credentials like this:
- On boot, it tries to load a previously saved SSID/password from NVS
(flash), namespace
wifi_cfg. - If none are stored yet (first boot), it prints
No WiFi config present.and prompts for an SSID and password over the serial console instead of using the compiled-in defaults. - It then attempts to connect, retrying up to
CONFIG_ESP_PPP_WIFI_MAXIMUM_RETRYtimes (set underidf.py menuconfig -> esp_ppp Configuration). If it still fails, it printsFailed to connect to "<ssid>".and prompts again for a new SSID/password, repeating until a connection succeeds. - Once connected with credentials that were entered interactively (as opposed to ones freshly loaded from NVS), it saves them to NVS so they're used automatically on subsequent boots.
The credential prompt (--- WiFi setup ---) reads from the same serial
port as the console/log output (idf.py monitor): type the SSID, press
Enter, then type the password (echoed as * characters), press Enter.
Backspace/delete is supported while typing.
An LED on GPIO10 (active low) reflects WiFi/PPP connection state:
| State | LED behavior |
|---|---|
| Waiting for credentials input / connect retries exhausted | Fast blink (100 ms) |
| WiFi connected, waiting for PPP client to dial in | Slow blink (500 ms) |
PPP client connected (IP_EVENT_PPP_GOT_IP) |
Solid on |
PPP link lost (IP_EVENT_PPP_LOST_IP) |
Back to slow blink |
The ESP32 acts as PPP server with a fixed address scheme:
- Server (ESP32) address:
192.168.11.1 - Client (peer MCU) address:
192.168.11.2 - No PPP authentication (no PAP/CHAP)
Configure the peer's PPP client to use those addresses (or accept them via
IPCP negotiation) and route default traffic over the PPP link. The ESP32
does not negotiate a DNS server address, so the peer should use a fixed
public resolver (e.g. 8.8.8.8) or IP literals.
app_main.c's eppp_listen() call blocks until the peer dials in, then
enables NAPT on the PPP netif so all of the peer's traffic is translated
onto the WiFi connection.
You don't need real peer-MCU firmware to try this out — a Linux PC can act
as the PPP client itself, using the standard pppd daemon over a serial
link.
If the PPP UART is still on its default pins/port (UART0, GPIO1/3), it's
the same port as the board's built-in USB-serial adapter — just plug the
ESP32 into the PC and note the device node (typically /dev/ttyUSB0 or
/dev/ttyACM0). In that case, first move the ESP-IDF console off UART0
(idf.py menuconfig -> Component config -> ESP System Settings -> Channel
for console output), otherwise log messages will corrupt the PPP stream.
Alternatively, configure the PPP link on a separate UART (e.g. GPIO17/18) and wire that to a separate 3.3V USB-to-TTL adapter (cross TX/RX, common ground) as described under Wiring — this keeps the console available on the original port for debugging.
sudo apt install ppp
Flash and reset the ESP32 as usual (see Build above) and wait
for WiFi to connect; idf.py monitor (if the console is still reachable)
should print waiting for PPP client on UART....
Then, on the PC, bring up pppd as the client, using the fixed address
scheme the firmware expects:
sudo pppd /dev/ttyACM0 115200 192.168.11.2:192.168.11.1 noauth local nodetach debug nocrtscts
- Replace
/dev/ttyUSB0and115200with your actual device node and the configured baudrate. 192.168.11.2:192.168.11.1fixes the PC's own PPP address and the ESP32's address, matching what the firmware hard-codes.localtellspppdto ignore modem control lines, since this is a direct wired link, not a modem.nodetach debugkeeppppdin the foreground and log the negotiation; drop them for normal use.
A ppp0 interface should come up on the PC once negotiation succeeds, and
the ESP32 should log PPP client connected, NAT to WiFi enabled.
Check the link itself:
ping 192.168.11.1
Check that NAT onto the WiFi network works, without disturbing the PC's own default route:
sudo ip route add 8.8.8.8 via 192.168.11.1 dev ppp0
ping 8.8.8.8
To instead route all of the PC's traffic through the ESP32 (fully
exercising it as an internet gateway), add defaultroute to the pppd
command line in step 3.
sudo kill %pppd # or Ctrl+C the foreground pppd