Description
If you love Teensy, but wished it had Wi-Fi or Bluetooth capability like the ESP32 line, this Teensy 4.x ESP32-C5 stack can help bridge that gap.
The ESP32-C5 acts as a smart coprocessor and communicates with the Teensy over a high-speed serial link. If the ESP32-C5 is not being used for a project, it can be configured so that it has no impact on the Teensy and the Teensy still has full use of all its pins.
Conceptually this is the same as having a baseboard with both a Teensy and ESP32 on it connected together over a serial link as is done on some of our own baseboards, but physically it is condensed down to just the Teensy footprint which can be handy for many use cases.
The ESP32 used in this version is the very small 14-pin XIAO ESP32-C5 module soldered on top of the Teensy 4.0 or Teensy 4.1 NE.
The ESP32-C5 is the only ESP32 that supports the 5GHz Wi-Fi band in addition to the 2.4GHz band. The downside is that it also requires the use of an external antenna which is provided with the unit. The C6 version we offer doesn’t support 5GHz, but also doesn’t require an external antenna.
Please Note: Parts are kept in stock to build any configurations required. Backordered items will typically ship by the next business day.
Key Features of the XIAO ESP32-C5 module:
- Very small 14-pin footprint
- Excellent build quality
- Excellent documentation from the manufacturer
- 32-bit RISC-V processor operating at 240MHz
- 8MB Flash
- 384KB SRAM
- 2.4GHz and 5GHz dual-band Wi-Fi 6 subsystem
- Bluetooth Low Energy 5.0
- Zigbee, Thread, IEEE 802.15.4 support
- IEEE 802.11 a/b/g/n/ac/ax supporting Station, SoftAP, concurrent SoftAP_Station operation and promiscuous (monitor) mode
- External antenna required (provided)
- USB-C connector
Package Includes:
- Teensy 4.0 or Teensy 4.1 NE and XIAO ESP32-C5 preassembled in a vertical stack.
- Teensy 4.1 NE optionally configured with PSRAM or Flash installed and tested
KEY FEATURES OF THE TEENSY 4.x ESP32-C5 STACK:
- Provides the processing power of the Teensy 4.0 or Teensy 4.1 NE.
- Provides the wireless connectivity of the ESP32-C5 which includes the 5GHz band.
- Teensy 4.1 NE can optionally have 8MB PSRAM/16MB Flash, 16MB PRAM, 8MB PSRAM/256MB Flash or 32MB PSRAM installed and tested.
- High speed serial link for data sharing.
- The soldered configuration provides for a very robust assembly with good signal integrity between modules.
- Shared pins can either be controlled by the Teensy or the ESP32-C5.
- Compact size is perfect for embedding.
- It does require the use of an external antenna which is provided.
- 5V operation, 3.3V logic compatible.
Please Note: We offer both ESP32-C6 and ESP32-C5 versions of the stack. The C6 version can be found here.
This table lists the main features of each version.
Communication between the Teensy and ESP32 is handled over a serial link using the Teensy Serial5 pins (20 & 21) and Serial1 on the ESP32-C5 (GPIO8 & GPIO12) that are remapped to line up with the Teensy pins.
Note: You can also map the pins to use Serial1 on the Teensy if you want to keep pins 20 & 21 available for other uses.
The serial link has been tested at 4Mbps and found to be very reliable and can potentially operate up to 5Mbps.
This setup only consumes 2 of the Teensy pins. The GPIO1 and 3.3V pins on the ESP32-C5 are removed to avoid conflicts with the Teensy GND and 3.3V pins. The other ESP32-C5 pins are disconnected from internal logic and set as inputs in software to avoid any conflict with the other attached Teensy pins.
Conversely, the matching Teensy pins could be set as inputs and the ESP32-C5 functionality can drive those shared pins. This might be handy if it is desired to have the ESP32-C5 directly control an attached I2C display or sensor module. One nice feature of the ESP32 architecture is that almost any functionality can be remapped to almost any physical pin.
The C5 variant was chosen for this version primarily to provide 5GHz operation. As with the C6 version, it has good wireless functionality and none of the edge pins are used for boot functions, so there is no issue with the Teensy holding the pins in a state that would cause issues at boot.
This diagram shows the pinout overlay of the ESP32-C5 on the Teensy 4.1. The shared Teensy 4.0 pins would be the same.
Below is the pinout of the XIAO ESP32-C5 module with its default I/O definitions. Most of this I/O can be remapped to other pins.
The Teensy 4.1 versions can also be selected to have optional memory upgrades as well to include up to 32MB PSRAM and up to 256MB Flash.
Please note: These items can be backordered. We keep all parts in stock to build the different versions and backordered items will typically ship by the next business day.
Please also note: We can also build the Teensy 4.1 version of the ESP32 Stack based on our Fully Loaded for Prototyping System versions of Teensy 4.1 with bottom mounted I/O. In that case, the standard Teensy 4.1 is used since wired Ethernet becomes an option along with USB Host. If interested in one of these, email me with your requirements at [email protected].
Things to Consider
As with many things in life, there is no completely free lunch.
The ESP32 stack requires programming both the ESP32 and the Teensy rather than just programming the Teensy side as when working with a dumb peripheral.
The ESP32 module does physically block the USB Host port and hardwired Ethernet port on the Teensy 4.1. If using with a PCB baseboard, these can still exit from the bottom of the Teensy.
On the Teensy 4.0 stack, due to the more compact form factor, the NXP processor sits directly under the ESP32 module. For that reason, thermals can run warmer than on the Teensy 4.1. How warm will depend heavily on how the ESP32 is being used. Heavy transmit operations is the worst case for thermals.
It does require the external antenna to be connected. It can be the small wire one shipped with the unit or it can be replaced with a larger mounted antenna. Just be sure that it is 5GHz compatible if you plan to use that mode.
Important Notes About Power
The stack can be powered from either the USB on the Teensy or the ESP32 or by using the 5V pin on the Teensy. There is no power switching circuitry, so only one power source should be connected at a time.
If you want to connect USB cables to both the ESP32 and Teensy at the same time, one of the cables should have the power wires disconnected. The red wire in the cable can be cut and there are USB data extender cables with built-in power switches that can be handy for this purpose as well. Just make sure that they are a data cable and not just a charging cable.
As an option, the VUSB trace on the bottom of the Teensy can be cut. That disconnects power from the Teensy USB port so that both USB cables can be safely connected. In that case, the assembly needs to be powered from the ESP32 USB or the Teensy 5V pin, but not both at the same time.
The basic combo draws about 135mA at idle. The Teensy 4.1 can potentially draw up to 250mA during SD card accesses. The ESP32-C5 can potentially draw up to 360mA during heavy Wi-Fi transmit operations. Under worst case conditions, it might be possible to overload the USB 2.0 500mA maximum current rating. In that case, powering from USB 3.0 or Teensy 5V pin is recommended.
Getting Started with the Teensy 4.x ESP32-C5 Stack
Software Configuration
The section below illustrates the basic requirements for setting up the basic communication link between the Teensy and the ESP32-C5 as well as getting the extra ESP32-C5 pins out of the way of the mating Teensy pins to avoid contention issues.
ESP32-C5 side
The ESP32-C5 uses a USB-C connector so be sure that you have a USB-C data cable available. Many USB-C cables are power only cables for charging devices like cell phones. If you need a cable, they are available at the bottom of the page.
When using the Arduino IDE, select XIAO ESP32-C5 as the board type. It is way down the ESP32 list. Also remember to select the port it is attached to.
The ESP32-C5 basic programming just requires a couple of things to configure it for use on the Teensy.
First, the Serial1 port needs to be redirected to line up with the Teensy serial5 pins which are pins D7 and D8 on the ESP32-C5 and pins 20 and 21 on the Teensy. If you want to use Serial1 on the Teensy, then use these defines for the ESP32-C5 instead:
#define RX1 D2
#define TX1 D1
If you change to Serial1 on the Teensy, also be sure to change the gpio_reset_pin(GPIO_NUM_0); and (GPIO_NUM_25); to gpio_reset_pin(GPIO_NUM_12); and (GPIO_NUM_8);
Note: When referencing the ESP32-C5 pins, you can either use the pin label such as “D7” or the corresponding GPIO pin number which is “12” in this case.
In addition, even though the other pins will tend to come up as inputs by default, it is good practice to use the gpio_reset_pin command to ensure that the pins other than those used by Serial1 are disconnected from any internal logic on the ESP32-C5 and are set as inputs to ensure there are no possibility of conflict with the attached Teensy pins.
This code snippet accomplishes those tasks.
#define RX1 D7 // Teensy 4.x Serial5 is connected to serial port #1 #define TX1 D8 //#define RX1 D2 // Use these instead if you want to use Teensy Serial1 //#define TX1 D1 void setup() { Serial.begin(115200); // Disconnect unused I/O to avoid possible pin conflicts with Teensy // If using Serial1 on the Teensy, change the gpio_reset_pin from // GPIO_NUM_0 and GPIO_NUM_25 to GPIO_12 and GPIO_8 as noted below gpio_reset_pin(GPIO_NUM_0); // Change to GPIO_NUM_12 if using Serial1 on Teensy gpio_reset_pin(GPIO_NUM_1); gpio_reset_pin(GPIO_NUM_7); gpio_reset_pin(GPIO_NUM_9); gpio_reset_pin(GPIO_NUM_10); gpio_reset_pin(GPIO_NUM_11); gpio_reset_pin(GPIO_NUM_23); gpio_reset_pin(GPIO_NUM_24); gpio_reset_pin(GPIO_NUM_25); // Change to GPIO_NUM_8 if using Serial1 on Teensy //Remap Serial1 to line up with Teensy Serial5 or Serial1 running at 4Mbps Serial1.begin(4000000, SERIAL_8N1, RX1, TX1);
Teensy Side
On the Teensy side of the stack, the main requirement is simply to initialize the Serial5 (or Serial1) port for operation at the same baud rate that the ESP32-C5 is set for. In addition, it may be desired to implement serial buffers to help hold the data that is being passed. Whether to use a buffer or not and its size would depend on what kind of serial traffic is planned to be passed between the modules. If you aren’t using the ESP32-C5 on a project, you can just ignore it. If you need to reclaim the serial port it is attached to, just set those reset those GPIO pins as well on the ESP32-C5 instead of configuring them to be a serial port.
#define ESP32SERIAL Serial5 // ESP32 is attached to Serial5 port //#define ESP32SERIAL Serial1 // Use this instead to use Teensy Serial1 port // Create buffer to hold incoming characters from ESP32-S3 #define ESP32SERIAL_BUFFER_SIZE 1024 unsigned char esp32SerialBuffer[ESP32SERIAL_BUFFER_SIZE]; //=============================================================================== // Initialization //=============================================================================== void setup() { Serial.begin(115200); //Initialize USB serial port to computer ESP32SERIAL.begin(4000000); //Initialize Seria1 5 connected to ESP32 at 4Mbps // Setup extra memory for the ESP32 serial buffer to avoid overruns ESP32SERIAL.addMemoryForRead(esp32SerialBuffer, ESP32SERIAL_BUFFER_SIZE);
Note: When modifying the code in either the Teensy or the ESP32-C5, depending on the nature of the programs they may get out of sync, and so a power cycle may be needed to resync them.
Our Evaluation Results:
The Teensy 4.x ESP32-C5 Stacks provide a compact and mechanically sound setup and open up some interesting possibilities for the application of Teensy.
These stacks are built using extended length headers which are clipped off on the Teensy pins that do not mate with the ESP32-C5.
The ESP32-C5 is mounted on top primarily so that the Teensy 3.3V remains available, though that functionality could be replaced with the ESP32-C5 3.3V supply. The ESP32-C5 is mounted as high as possible on the pins to make room for both USB cables to potentially be connected at the same time.
One feature of this ESP32-C5 module is that it requires the use of an external antenna. This provides better range than the built-in antenna as used on the C6 version but if you don’t want to mess with an external antenna, then the C6 may be the better option for you, especially if you don’t need the 5GHz band.
The external antenna connector is a standard U.FL type. A 3″ wire antenna ships preinstalled, but it can be replaced with a 5HGz compatible mounted antenna if desired.
Per the ESP32-C5 documentation, 5Mbps is the maximum supported serial communications rate though the Teensy can theoretically go higher than that. In our testing, 4Mbps gives good high speed communications between the modules with virtually no errors. The test programs below can be used to play with that aspect of the product.
The ESP32-C5 module has a red LED that is connected to the built-in battery charging circuit that is not used in this application. It may come on when power is first applied and then will go off on its own after about 30 seconds.
There is also a yellow user LED. It is connected to GPIO27 and can be accessed using the usual LED_BUILTIN constant. GPIO27 does not come out to the edge pins and so does not conflict with the Teensy.
The Teensy line of boards are an excellent product that provides high performance and advanced I/O to tackle even the hardest problems which is why they are often found in advanced hobbyist projects as well as low volume production builds.
The PJRC forum provides access to excellent technical advice that is far more advanced than found on the normal Arduino forums. It is the best place to find information on how to use the advanced features found in the Teensy.
It is highly suggested to checkout the PJRC website and Teensy forum at the links below for additional information.
The XIAO ESP32 line by Seeed Studio is also a well supported and documented product and more information can be found at those links below as well.
Teensy ESP32-C5 Stack WiFi Scan Example Programs
The two programs below implement the WiFi Scan example. These programs are loaded into the Teensy and ESP32-C5 before shipment to serve as a verification of the basic functionality out of the box. Plug USB into the Teensy and open the Serial Monitor to see the output.
Teensy 4.0 / 4.1 WiFi Scan Example Program
The Teensy program checks for installed PSRAM or Flash upgrades if it is compiled to run on a Teensy 4.1. It then checks to see if the ESP32 is connected. It then loops every 5 seconds, requesting a scan of wireless networks and reports the results out to the USB serial port.
/* Teensy / ESP32 Scan Example Program Also reports any PSRAM or Flash mounted on a Teensy 4.1 and reports the CPU temperature */ //Included if compiling for Teensy 4.1 and not for Teensy 4.0 #if defined(ARDUINO_TEENSY41) #include "LittleFS.h" extern "C" uint8_t external_psram_size; #endif #define ESP32SERIAL Serial5 // ESP32 is attached to Serial5 port //#define ESP32SERIAL Serial1 // Use this instead to use Teensy Serial1 port // Create buffer to hold incoming characters from ESP32 #define ESP32SERIAL_BUFFER_SIZE 1024 unsigned char esp32SerialBuffer[ESP32SERIAL_BUFFER_SIZE]; bool esp32Attached = false; // Flag if ESP32 is attached bool scanRequested = false; // Flag if scan is in process //=============================================================================== // Initialization //=============================================================================== void setup() { Serial.begin(115200); //Initialize Seria1 5 or 1 connected to ESP32 at 4Mbps ESP32SERIAL.begin(4000000); // Setup extra memory for the ESP32 serial buffer to avoid overruns ESP32SERIAL.addMemoryForRead(esp32SerialBuffer, ESP32SERIAL_BUFFER_SIZE); // If running on a Teensy 4.1, check if PSRAM or Flash is installed #if defined(ARDUINO_TEENSY41) // Check for PSRAM chip(s) installed on Teensy 4.1 uint8_t size = external_psram_size; if (size == 0) { Serial.println("No PSRAM Installed"); } else { Serial.printf("PSRAM Memory Size = %d Mbyte\n", size); } // Check for Flash chip installed on Teensy 4.1 LittleFS_QSPI myfs; // Works with both types of flash // Check for any Flash chip installed. if (myfs.begin()) { Serial.printf("Flash Memory Size = %d Mbyte / ", myfs.totalSize() / 1048576); Serial.printf("%d Gbit\n", myfs.totalSize() / 131072000); } else { Serial.printf("No Flash Installed\n"); } #endif // Check for ESP32 installed while (ESP32SERIAL.available()) { // Clear ESP32 input buffer before CMD (char)ESP32SERIAL.read(); } ESP32SERIAL.print("?"); // Ask ESP32 if it is there delay(100); // Wait a bit for ESP32 to respond if (ESP32SERIAL.available()) { // If there is a response String returnData = ESP32SERIAL.readString(); // Read response if (returnData == 'Y') { // ESP32 responded with 'Y'es, I'm here esp32Attached = true; Serial.println("ESP32 was found"); } else { // invalid response received Serial.println("ESP32 invalid response - Check that baud rates match"); esp32Attached = false; } } else { esp32Attached = false; Serial.println("ESP32 did not respond - Stop execution"); } if (!esp32Attached) while (true); // Don't proceed if it doesn't find ESP32 } //=============================================================================== // Main //=============================================================================== void loop() { DoScan(); if (scanRequested) { // Wait until ESP32 sends something back while (ESP32SERIAL.available() < 1) { delay(1); } Serial.println("Read incoming data"); while (ESP32SERIAL.available()) { String returnData = ESP32SERIAL.readStringUntil('\n'); Serial.println(returnData); delay(10); } scanRequested = false; delay(5000); } } //=============================================================================== // Routine to request Scan from ESP32 //=============================================================================== void DoScan() { ESP32SERIAL.println("S"); // Send command to ESP32 to start scan scanRequested = true; // Set flag that we requested scan Serial.println("Scan being requested"); }
ESP32-C5 WiFi Scan Example Program
The ESP32 program just implements the Wifi Scan example program which scans for 2.4GHz and 5GHz networks along with the changes needed to work with this piggyback configuration. Instead of sending the output to the USB port, it sends it to the Teensy over the Serial1 port.
/* This sketch interfaces to the Teensy 4.x via serial port 1 which is remapped to line up with Teensy Serial5 or Serial1 pins. It demonstrates how to receive a request for a scan for WiFi networks and report the results back via serial port. This is a simple variation of the ESP32-C5 WiFiScan example program designed for chips that support 5GHz band. Separate scans are done for all bands. Select board type as XIAO ESP32-C5 in the IDE */ #include <Arduino.h> #include "WiFi.h" #define RX1 D7 // Teensy 4.x Serial5 is connected to serial port #1 #define TX1 D8 //#define RX1 D2 // Use these instead if you want to use Teensy Serial1 //#define TX1 D1 void setup() { Serial.begin(115200); // Disconnect unused I/O to avoid possible pin conflicts with Teensy // If using Serial1 on the Teensy, change the gpio_reset_pin from // GPIO_NUM_0 and GPIO_NUM_25 to GPIO_12 and GPIO_8 as noted below gpio_reset_pin(GPIO_NUM_0); // Change to GPIO_NUM_12 if using Serial1 on Teensy gpio_reset_pin(GPIO_NUM_1); gpio_reset_pin(GPIO_NUM_7); gpio_reset_pin(GPIO_NUM_9); gpio_reset_pin(GPIO_NUM_10); gpio_reset_pin(GPIO_NUM_11); gpio_reset_pin(GPIO_NUM_23); gpio_reset_pin(GPIO_NUM_24); gpio_reset_pin(GPIO_NUM_25); // Change to GPIO_NUM_8 if using Serial1 on Teensy //Remap Serial1 to line up with Teensy Serial5 or Serial1 running at 4Mbps Serial1.begin(4000000, SERIAL_8N1, RX1, TX1); // Enable Station Interface WiFi.STA.begin(); Serial.println("Setup done"); } void ScanWiFi() { Serial1.println("Scan start"); // WiFi.scanNetworks will return the number of networks found. int n = WiFi.scanNetworks(); Serial1.println("Scan done"); if (n == 0) { Serial1.println("no networks found"); } else { Serial1.print(n); Serial1.println(" networks found"); Serial1.println("Nr | SSID | RSSI | CH | Encryption"); for (int i = 0; i < n; ++i) { // Print SSID and RSSI for each network found Serial1.printf("%2d", i + 1); Serial1.print(" | "); Serial1.printf("%-32.32s", WiFi.SSID(i).c_str()); Serial1.print(" | "); Serial1.printf("%4" PRIi32, WiFi.RSSI(i)); Serial1.print(" | "); Serial1.printf("%2" PRIi32, WiFi.channel(i)); Serial1.print(" | "); switch (WiFi.encryptionType(i)) { case WIFI_AUTH_OPEN: Serial1.print("open"); break; case WIFI_AUTH_WEP: Serial1.print("WEP"); break; case WIFI_AUTH_WPA_PSK: Serial1.print("WPA"); break; case WIFI_AUTH_WPA2_PSK: Serial1.print("WPA2"); break; case WIFI_AUTH_WPA_WPA2_PSK: Serial1.print("WPA+WPA2"); break; case WIFI_AUTH_WPA2_ENTERPRISE: Serial1.print("WPA2-EAP"); break; case WIFI_AUTH_WPA3_PSK: Serial1.print("WPA3"); break; case WIFI_AUTH_WPA2_WPA3_PSK: Serial1.print("WPA2+WPA3"); break; case WIFI_AUTH_WAPI_PSK: Serial1.print("WAPI"); break; default: Serial1.print("unknown"); } Serial1.println(); delay(10); } } // Delete the scan result to free memory for code below. WiFi.scanDelete(); Serial1.println("-------------------------------------"); } void loop() { if (Serial1.available()) { char command = Serial1.read(); Serial.println(command); if (command == '?') { // Are you there? Serial1.print("Y"); // Acknowledge I'm attached } if (command == 'S') { Serial1.println("-------------------------------------"); Serial1.println("Default wifi band mode scan:"); Serial1.println("-------------------------------------"); #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 2) WiFi.setBandMode(WIFI_BAND_MODE_AUTO); #endif ScanWiFi(); #if CONFIG_SOC_WIFI_SUPPORT_5G // Wait a bit before scanning again. delay(1000); Serial1.println("-------------------------------------"); Serial1.println("2.4 Ghz wifi band mode scan:"); Serial1.println("-------------------------------------"); WiFi.setBandMode(WIFI_BAND_MODE_2G_ONLY); ScanWiFi(); // Wait a bit before scanning again. delay(1000); Serial1.println("-------------------------------------"); Serial1.println("5 Ghz wifi band mode scan:"); Serial1.println("-------------------------------------"); WiFi.setBandMode(WIFI_BAND_MODE_5G_ONLY); ScanWiFi(); #endif // Wait a bit before scanning again. delay(10000); } } }
Before they are shipped, these modules are:
- Any PSRAM or Flash chips are installed depending on the configuration ordered.
- Boards are soldered together.
- Flux residue is cleaned.
- Any installed memory is tested.
- Antenna is installed
- Basic functionality of the stack is tested by installing WiFi example before shipment.
- Packaged in resealable ESD bag for safe storage.
Further Reading:
You can read more about the basic Teensy hardware on our product pages.
Highly recommended to also checkout the PJRC Teensy website and forum.
PJRC Forum – Excellent source of technical information
PJRC Teensy Forum post about these stacks
WIKI for Seeed Studio ESP32-C5 – Excellent source of information about the module with programming examples.
Notes:
- None
Technical Specifications
| Operating Voltage | 3.3V |
| Typical Operating Current | 135mA (typical) at idle Teensy 4.1 max draw is up to 250mA (SD card access ESP32-C5 max draw is up to 380mA (Transmit) |
| Board Dimensions | |
| Teensy 4.0 ESP32 Stack | 38x 18mm (1.4 x 1.5″) |
| Teensy 4.1 ESP32 Stack | 65 x 18mm (2.4 x 1.5″) |
| Stacked Height (not including Teensy pins | 14mm (0.55″) |
| Country of Origin | Teensy and final assembly is USA XIAO ESP32-C5 is China |
















