La Shell de Zephyr es una consola interactiva para inspeccionar y controlar el firmware en ejecución.
Hasta ahora, cuando compilamos el firmware, es una “caja negra”. Lo flasheamos y esperamos que funcione. Si algo falla, ponemos printk y rezamos.
Pero, ¿y si pudierais “entrar” dentro del microcontrolador mientras está funcionando? ¿Y si pudierais preguntarle: “¿Cuánta memoria libre queda?”, “¿Qué hilos están corriendo?” o “Enciende el LED 2” escribiendo comandos en una terminal?
Este subsistema convierte vuestra conexión UART (o USB) en una línea de comandos muy parecida a la de Linux o DOS.
Activar la shell
La Shell es un módulo opcional. Para activarla, solo necesitamos añadir un par de líneas a nuestro prj.conf.
# Activamos el subsistema Shell
CONFIG_SHELL=y
# Elegimos el backend (por dónde sale). Normalmente UART.
CONFIG_SHELL_BACKEND_SERIAL=y
# Comandos de kernel y dispositivos usados en este artículo
CONFIG_KERNEL_SHELL=y
CONFIG_DEVICE_SHELL=y
CONFIG_THREAD_MONITOR=y
# Historial navegable con las flechas del teclado
CONFIG_SHELL_HISTORY=y
# Opcional: Aumentar el buffer si vamos a escribir comandos largos
CONFIG_SHELL_CMD_BUFF_SIZE=128Al recompilar y flashear (west build -p always ...), abre el monitor serie (PuTTY, Minicom o la extensión de VSCode) y pulsa Enter. Verás un prompt interactivo:
uart:~$¡Ya estamos dentro!
Comandos integrados
Los comandos disponibles dependen de las opciones Kconfig activadas. Escribe help para ver los que incluye tu compilación.
kernel version y kernel uptime
Lo básico. Nos dice qué versión del OS corre y cuánto tiempo lleva encendido.
kernel threads
Este es el comando más importante. Muestra una tabla en tiempo real con todos los hilos del sistema, su uso de pila (Stack) y su prioridad.
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 readySi ves que el porcentaje de stack (%) de algún hilo se acerca al 90 %, estás a punto de sufrir un desbordamiento. Aumenta el stack en el código.
device list
Muestra los dispositivos registrados y su estado. Las opciones necesarias ya están incluidas en el prj.conf anterior: CONFIG_KERNEL_SHELL=y, CONFIG_DEVICE_SHELL=y y CONFIG_THREAD_MONITOR=y.
Historial
Con CONFIG_SHELL_HISTORY=y, puedes usar las flechas arriba y abajo para recuperar comandos anteriores.
Shells de periféricos
Además de los comandos del Kernel, podemos activar comandos específicos para probar hardware sin escribir código C. Esto es utilísimo para validar una PCB recién fabricada.
En prj.conf:
CONFIG_GPIO_SHELL=y
CONFIG_I2C_SHELL=yAhora puedes hacer cosas como:
- Leer un pin:
gpio get gpio@50000000 13(Leemos el pin 13 del puerto 0). - Escanear I2C:
i2c scan i2c@40003000(Busca dispositivos en el bus).
Los nombres extraños como gpio@50000000 son las etiquetas internas de los dispositivos. Puedes ver los nombres correctos usando device list.
Crear comandos propios
Lo mejor de la Shell es que puedes añadir tus propios comandos personalizados.
Vamos a crear un comando app status para imprimir variables de la aplicación y otro app say_hello que reciba un argumento.
El código necesario en tu main.c es sorprendentemente sencillo:
#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
/* Función que se ejecuta con el comando 'status' */
static int cmd_status(const struct shell *sh, size_t argc, char **argv)
{
/* Usamos shell_print en lugar de printk para que salga por la shell */
shell_print(sh, "Estado del sistema: OK. Temperatura: 24.5C");
return 0;
}
/* Función que se ejecuta con el comando 'say_hello' */
/* argc es el número de argumentos, argv es el array de argumentos */
static int cmd_say_hello(const struct shell *sh, size_t argc, char **argv)
{
/* argv[0] es el nombre del comando. argv[1] es el primer argumento. */
shell_print(sh, "Hola %s!", argv[1]);
return 0;
}
/* Definimos los subcomandos (el nivel más bajo) */
SHELL_STATIC_SUBCMD_SET_CREATE(sub_app,
SHELL_CMD(status, NULL, "Muestra el estado", cmd_status),
SHELL_CMD_ARG(say_hello, NULL, "Saluda a alguien. Uso: say_hello <nombre>", cmd_say_hello, 2, 0),
SHELL_SUBCMD_SET_END /* Marca el final de la lista */
);
/* Registramos el comando raíz 'app' */
SHELL_CMD_REGISTER(app, &sub_app, "Comandos de mi aplicación", NULL);
int main(void) {
/* ... resto del código ... */
return 0;
}Ahora, al compilar y ejecutar, puedes escribir:
uart:~$ app status
Estado del sistema: OK. Temperatura: 24.5C
uart:~$ app say_hello Luis
Hola Luis!Colores y formato
La Shell soporta códigos de escape VT100. Esto significa que puedes imprimir en colores.
shell_info(sh, "Esto es información (Verde)");
shell_warn(sh, "Esto es un warning (Amarillo)");
shell_error(sh, "Esto es un error (Rojo)");