zephyr-herramientas-shell

Zephyr Shell: Interactive Command Console

  • 4 min

The Zephyr Shell is an interactive console to inspect and control running firmware.

So far, when we compile the firmware, it’s a “black box.” We flash it and hope it works. If something fails, we use printk and cross our fingers.

But what if you could “enter” the microcontroller while it is running? What if you could ask it: “How much free memory is left?”, “Which threads are running?” or “Turn on LED 2” by typing commands in a terminal?

This subsystem turns your UART (or USB) connection into a command line very similar to Linux or DOS.

Activating the Shell

The Shell is an optional module. To activate it, we just need to add a couple of lines to our prj.conf.

# Activate the Shell subsystem
CONFIG_SHELL=y

# Choose the backend (output channel). Usually UART.
CONFIG_SHELL_BACKEND_SERIAL=y

# Kernel and device commands used in this article
CONFIG_KERNEL_SHELL=y
CONFIG_DEVICE_SHELL=y
CONFIG_THREAD_MONITOR=y

# Navigable history with keyboard arrows
CONFIG_SHELL_HISTORY=y

# Optional: Increase buffer for long commands
CONFIG_SHELL_CMD_BUFF_SIZE=128
Copied!

After recompiling and flashing (west build -p always ...), open the serial monitor (PuTTY, Minicom, or the VSCode extension) and press Enter. You will see an interactive prompt:

uart:~$
Copied!

We are in!

Built-in Commands

The available commands depend on which Kconfig options are enabled. Type help to see the commands included in your build.

kernel version and kernel uptime

The basics. Tells you the OS version and how long it has been running.

kernel threads

This is the most important command. It displays a real-time table of all system threads, their stack usage, and priority.

uart:~$ kernel threads
Scheduler: 335 since last call
 Thread       Pri   Stack   %   Of   State
*idle          48     128  33  384   running
 main           0     420  20 2048   suspend
 logging       14     200  19 1024   suspend
 shell_uart    14     512  25 2048   ready
Copied!

If you see that the stack usage percentage (%) of any thread is approaching 90%, you are about to experience a stack overflow. Increase the stack in the code.

device list

Shows registered devices and their status. The necessary options are already included in the prj.conf above: CONFIG_KERNEL_SHELL=y, CONFIG_DEVICE_SHELL=y and CONFIG_THREAD_MONITOR=y.

History

With CONFIG_SHELL_HISTORY=y, you can use the up and down arrow keys to recall previous commands.

Peripheral Shells

In addition to Kernel commands, we can activate specific commands to test hardware without writing C code. This is extremely useful for validating a newly manufactured PCB.

In prj.conf:

CONFIG_GPIO_SHELL=y
CONFIG_I2C_SHELL=y
Copied!

Now you can do things like:

  • Read a pin: gpio get gpio@50000000 13 (Reads pin 13 of port 0).
  • Scan I2C: i2c scan i2c@40003000 (Searches for devices on the bus).

Strange names like gpio@50000000 are the internal device labels. You can see the correct names using device list.

Creating Custom Commands

The best part of the Shell is that you can add your own custom commands.

Let’s create an app status command to print application variables and an app say_hello command that receives an argument.

The necessary code in your main.c is surprisingly simple:

#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>

/* Function executed with the 'status' command */
static int cmd_status(const struct shell *sh, size_t argc, char **argv)
{
    /* Use shell_print instead of printk so it outputs via the shell */
    shell_print(sh, "System status: OK. Temperature: 24.5C");
    return 0;
}

/* Function executed with the 'say_hello' command */
/* argc is the number of arguments, argv is the argument array */
static int cmd_say_hello(const struct shell *sh, size_t argc, char **argv)
{
    /* argv[0] is the command name. argv[1] is the first argument. */
    shell_print(sh, "Hello %s!", argv[1]);
    return 0;
}

/* Define the subcommands (lowest level) */
SHELL_STATIC_SUBCMD_SET_CREATE(sub_app,
    SHELL_CMD(status, NULL, "Shows status", cmd_status),
    SHELL_CMD_ARG(say_hello, NULL, "Greets someone. Usage: say_hello <name>", cmd_say_hello, 2, 0),
    SHELL_SUBCMD_SET_END /* Marks the end of the list */
);

/* Register the root command 'app' */
SHELL_CMD_REGISTER(app, &sub_app, "My application commands", NULL);

int main(void) {
    /* ... rest of code ... */
    return 0;
}
Copied!

Now, after compiling and running, you can type:

uart:~$ app status
System status: OK. Temperature: 24.5C

uart:~$ app say_hello Luis
Hello Luis!
Copied!

Colors and Formatting

The Shell supports VT100 escape codes. This means you can print in colors.

shell_info(sh, "This is information (Green)");
shell_warn(sh, "This is a warning (Yellow)");
shell_error(sh, "This is an error (Red)");
Copied!