Like Linux, Zephyr can route serial console streams to various hardware interfaces. This guide shows how to run the Zephyr Shell sample program on a QT Py ESP32-S3 using either USB serial or BLE with the Adafruit Bluefruit Connect app.
Prerequisites
To follow along with this guide, you will need:
- Linux computer running Debian 12, Ubuntu 22 LTS, Ubuntu 24 LTS, or similar
- Zephyr workspace including west and the Zephyr SDK's esp32-s3 toolchain (my Getting Started with Zephyr on Linux Playground guide explains how to set this up)
- Adafruit QT Py ESP32-S3 dev board (8flash/nopsram) with Zephyr bootloader (or you can use the ESP32 BOOT+RESET procedure to flash the Zephyr bootloader for the first time)
- iPhone with the Adafruit Bluefruit Connect iOS app.
What is the Zephyr Shell?
The main Zephyr repository on GitHub comes with many sample programs. This guide uses the zephyr/samples/subsys/shell/shell_module/ sample which is commonly referred to in the Zephyr community as "the shell".
The Zephyr shell works like a typical serial console REPL, providing interactive commands that are useful for exploring supported features on Zephyr boards. You can send the help command to get a list of available commands. Also, similar to Linux, Zephyr allows for routing the console's serial stream to different "backend" hardware interfaces.
The QT Py ESP32-S3 board definition uses USB serial by default, so you can easily access the Zephyr console by running west espressif monitor. With some small changes to the code and build command, it is also possible to route the console over Bluetooth LE Nordic UART Service (BLE NUS), which works with Adafruit's Bluefruit Connect iOS app.
USB Serial Shell
This is easy because Zephyr's QT Py ESP32-S3 board definition is set up to use USB serial for its console.
Build and Flash the USB Shell
To build and flash the shell firmware configured to use USB serial, run these commands in a Debian 12 terminal:
cd ~/code/zephyr-workspace/ source .venv/bin/activate cd zephyr/ west build -p always -b adafruit_qt_py_esp32s3/esp32s3/procpu samples/subsys/shell/shell_module/ west flash
Run USB Shell Commands
This is an example USB serial monitor session with west espressif monitor demonstrating how to run some shell commands. I've edited out normal the serial monitor startup messages so it's easier to see the shell stuff:
(.venv) $ west espressif monitor
[...lots of startup messages edited out]
uart:~$
uart:~$ help
Please press the <Tab> button to see all available commands.
You can also use the <Tab> button to prompt or auto-complete all commands or its subcommands.
You can try to call commands with <-h> or <--help> parameter for more information.
Shell supports following meta-keys:
Ctrl + (a key from: abcdefklnpuw)
Alt + (a key from: bf)
Please refer to shell documentation for more details.
Available commands:
bypass : Bypass shell
clear : Clear screen.
date : Date commands
demo : Demo commands
device : Device commands
devmem : Read/write physical memory
Usage:
Read memory at address with optional width:
devmem <address> [<width>]
Write memory at address with mandatory width and value:
devmem <address> <width> <value>
dynamic : Demonstrate dynamic command usage.
help : Prints the help message.
history : Command history.
kernel : Kernel commands
log : Commands for controlling logger
log_test : Log test
rem : Ignore lines beginning with 'rem '
resize : Console gets terminal screen size or assumes default in
case the readout fails. It must be executed after each
terminal width change to ensure correct text display.
retval : Print return value of most recent command
section_cmd : Demo command using section for subcommand registration
shell : Useful, not Unix-like shell commands.
shell_uart_release : Uninitialize shell instance and release uart, start
loopback on uart. Shell instance is reinitialized when
'x' is pressed
stats : Stats commands
version : Show kernel version
uart:~$ stats
stats - Stats commands
Subcommands:
list : List stats
uart:~$ version
Zephyr version 4.0.99
uart:~$ date
date - Date commands
Subcommands:
set : [Y-m-d] <H:M:S>
get : [none]
uart:~$ device
device - Device commands
Subcommands:
list : List configured devices
uart:~$ device list
devices:
- rtc@60021000 (READY)
DT node labels: rtc
- gpio@60004800 (READY)
DT node labels: gpio1
- gpio@60004000 (READY)
DT node labels: gpio0
- uart@60038000 (READY)
DT node labels: usb_serial
- uart@60000000 (READY)
DT node labels: uart0 xiao_serial
uart:~$
BLE Nordic UART Service (NUS) Shell
Building the shell for BLE serial is a little more complicated because the ESP32-S3 HAL code expects to have its console using USB serial. But, that's easy to fix with a minor patch.
For background and context on the -S nus-console west build option to re-route the console stream, you might want to check out:
- Video: Open-Source Contribution Update: Shell Over BLE - Luis Ubieda, Croxel, Inc.
- Source code: https://github.com/zephyrproject-rtos/zephyr/tree/main/snippets/nus-console
- Snippet docs: https://docs.zephyrproject.org/latest/snippets/nus-console/README.html
Patch the ESP32-S3 SoC Init Code
Currently (Jan 13, 2025), if you try this with the unpatched ESP32-S3 init code, it doesn't work:
west build -p always -b adafruit_qt_py_esp32s3/esp32s3/procpu -S nus-console samples/subsys/shell/shell_module/
When I tried it, I got this error:
FAILED: zephyr/zephyr_pre0.elf zephyr/zephyr_pre0.map [...lots of stuff edited out...]/home/sam/code/zephyr-workspace/zephyr/soc/espressif/esp32s3/hw_init.c:53: undefined reference to `esp_console_init'
The error is happening because zephyrproject-rtos/zephyr/soc/espressif/esp32s3/hw_init.c unconditionally makes a call to esp_console_init(), while the definition of esp_console_init() happens inside of an #ifdef CONFIG_ESP_CONSOLE_UART block in zephyrproject-rtos/hal_espressif/zephyr/common/console_init.c. When you specify the nus-console snippet option, it turns off that definition block, so the linker can't find esp_console_init().
One easy fix is to use your favorite editor to add an ifdef block around the esp_console_init() call in zephyr/soc/espressif/esp32s3/hw_init.c, so it looks like this:
(.venv) $ git diff
diff --git a/soc/espressif/esp32s3/hw_init.c b/soc/espressif/esp32s3/hw_init.c
index 97ffb0ef..15cbb9dc 100644
--- a/soc/espressif/esp32s3/hw_init.c
+++ b/soc/espressif/esp32s3/hw_init.c
@@ -58,7 +58,9 @@ int hardware_init(void)
bootloader_clock_configure();
/* initialize console, from now on, we can log */
+#ifdef CONFIG_ESP_CONSOLE_UART
esp_console_init();
+#endif /* CONFIG_ESP_CONSOLE_UART */
print_banner();
spi_flash_init_chip_state();
Build and Flash BLE NUS Shell
Once the code is patched, you can build and flash the shell with BLE NUS console using these commands in a Debian 12 terminal:
west build -p always -b adafruit_qt_py_esp32s3/esp32s3/procpu -S nus-console samples/subsys/shell/shell_module/ west flash
Run Shell Commands with Bluefruit Connect
Now that the console is re-routed to the Bluetooth LE Nordic UART Service, west espressif monitor won't work anymore. Instead, we can use the Adafruit Bluefruit Connect iOS app.
First, you need to install the app, enable bluetooth, and so on. If you need more instructions, you can check out the Bluefruit LE Connect for iOS and Android learn guide.
Next open the app. On the Select Device screen, enable the "Must have UART Service" option, then reset the ESP32-S3 board so it will send an advertisement. You should see a "Zephyr" device, like this:
Tap the Connect button. You should see a screen like this:
Pick the UART option to open the UART terminal screen. When the UART terminal first opens, it may be in "timestamp" mode, where each BLE UART message goes on its own line with a timestamp on the left. If that happens, the first screen of output from the Zephyr shell will look pretty confusing. Don't worry though, because it's easy to fix.
To fix the terminal display, tap the gear icon to open settings, then select "Terminal" from the "Display Mode" list, like this:
Once you've set the display mode to terminal, you should see about a half-screen of output from the Zephyr shell. It will probably be difficult to read because of garbage characters from ANSI escape codes.
You can send the shell colors off command to turn off the ANSI escape codes. Once you do that, the shell output should be much easier to read, like this:
Once you turn off the escape codes, it's much more comfortable to try other commands and explore a bit. For example, the device list command shows information about the current firmware's Devicetree configuration:
This page (Zephyr Shell over USB or BLE + Bluefruit Connect) was last updated on January 13, 2025.