返回首页

Zephyr Shell 子系统学习笔记

概述

Zephyr Shell 是一个强大的命令行接口,用于调试、配置和控制系统。

核心概念

1. Shell 架构

┌─────────────────────────────────────┐
│           User Input                │
│        (UART/RTT/USB)               │
└──────────────┬──────────────────────┘
               │
┌──────────────▼──────────────────────┐
│         Shell Core                  │
│  ┌─────────────────────────────┐   │
│  │    Command Parser           │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │    Command Dispatcher       │   │
│  └─────────────────────────────┘   │
│  ┌─────────────────────────────┐   │
│  │    Output Formatter         │   │
│  └─────────────────────────────┘   │
└─────────────────────────────────────┘
               │
┌──────────────▼──────────────────────┐
│         Shell Backends              │
│  UART | RTT | USB | Telnet          │
└─────────────────────────────────────┘

2. Shell 配置

prj.conf 基本配置:

# 启用 Shell
CONFIG_SHELL=y

# 启用 UART 后端
CONFIG_SHELL_BACKEND_SERIAL=y

# 启用 RTT 后端 (调试用)
CONFIG_SHELL_BACKEND_RTT=y

# 启用日志
CONFIG_LOG=y
CONFIG_SHELL_LOG_BACKEND=y

# 命令历史
CONFIG_SHELL_HISTORY=y

# Tab 补全
CONFIG_SHELL_TAB=y
CONFIG_SHELL_TAB_AUTOCOMPLETION=y

# 参数自动补全
CONFIG_SHELL_CMDS_SELECT=y

# 颜色输出
CONFIG_SHELL_VT100_COLORS=y

Shell 命令定义

1. 基本命令

#include <zephyr/shell/shell.h>

/* 简单命令 */
static int cmd_hello(const struct shell *sh, size_t argc, char **argv)
{
    shell_print(sh, "Hello, Zephyr Shell!");
    return 0;
}

/* 注册命令 */
SHELL_CMD_REGISTER(hello, NULL, "Say hello", cmd_hello);

2. 带参数的命令

static int cmd_echo(const struct shell *sh, size_t argc, char **argv)
{
    for (size_t i = 1; i < argc; i++) {
        shell_print(sh, "argv[%d]: %s", i, argv[i]);
    }
    return 0;
}

SHELL_CMD_REGISTER(echo, NULL, "Echo arguments", cmd_echo);

3. 子命令

/* LED 控制子命令 */
static int cmd_led_on(const struct shell *sh, size_t argc, char **argv)
{
    shell_print(sh, "LED ON");
    return 0;
}

static int cmd_led_off(const struct shell *sh, size_t argc, char **argv)
{
    shell_print(sh, "LED OFF");
    return 0;
}

/* 子命令数组 */
SHELL_STATIC_SUBCMD_SET_CREATE(sub_led,
    SHELL_CMD(on, NULL, "Turn LED on", cmd_led_on),
    SHELL_CMD(off, NULL, "Turn LED off", cmd_led_off),
    SHELL_SUBCMD_SET_END
);

/* 注册主命令 */
SHELL_CMD_REGISTER(led, &sub_led, "LED control commands", NULL);

4. 带选项的命令

static int cmd_config(const struct shell *sh, size_t argc, char **argv)
{
    /* 获取选项值 */
    char *value = strchr(argv[1], '=');
    if (value) {
        value++;  /* 跳过 '=' */
        shell_print(sh, "Config value: %s", value);
    }
    return 0;
}

SHELL_CMD_REGISTER(config, NULL, "Set configuration", cmd_config);

Shell 输出函数

1. 基本输出

/* 打印普通信息 */
shell_print(sh, "Normal message");

/* 打印警告 */
shell_warn(sh, "Warning message");

/* 打印错误 */
shell_error(sh, "Error message");

/* 打印十六进制数据 */
shell_hexdump(sh, data, sizeof(data));

/* 打印信息提示 */
shell_info(sh, "Info message");

/* 打印普通文本(无前缀) */
shell_fprintf(sh, SHELL_NORMAL, "Raw output\n");

2. 格式化输出

/* 支持 printf 格式 */
shell_print(sh, "Temperature: %d.%d C", temp_int, temp_frac);
shell_print(sh, "Device: %s, Address: 0x%08X", name, addr);

Shell 后端配置

1. UART 后端

/* 默认串口后端一般由控制台 / chosen 节点决定 */
#define SHELL_UART_DEV DEVICE_DT_GET(DT_CHOSEN(zephyr_shell_uart))

/* 自定义 UART 设备 */
static const struct shell_uart_cfg shell_uart_cfg = {
    .dev = SHELL_UART_DEV,
};

SHELL_UART_DEFINE(shell_uart, shell_uart_cfg);

2. RTT 后端

# prj.conf
CONFIG_USE_SEGGER_RTT=y
CONFIG_SHELL_BACKEND_RTT=y
CONFIG_RTT_CONSOLE=y

3. USB CDC 后端

# prj.conf
# legacy USB device stack 常见写法
# CONFIG_USB_DEVICE_STACK=y
# CONFIG_USB_CDC_ACM=y

# current USB device stack 参考当前 sample / 文档
CONFIG_USB_DEVICE_STACK_NEXT=y
CONFIG_SHELL_BACKEND_SERIAL=y
CONFIG_UART_LINE_CTRL=y

4. Telnet 后端

# prj.conf
CONFIG_NET_IPV4=y
CONFIG_NET_TCP=y
CONFIG_SHELL_BACKEND_TELNET=y
CONFIG_SHELL_TELNET_INIT_LOG_LEVEL_DBG=y

高级功能

1. 参数验证

static int cmd_set_value(const struct shell *sh, size_t argc, char **argv)
{
    if (argc < 2) {
        shell_error(sh, "Missing argument");
        shell_help(sh);
        return -EINVAL;
    }
    
    int value = atoi(argv[1]);
    if (value < 0 || value > 100) {
        shell_error(sh, "Value must be 0-100");
        return -EINVAL;
    }
    
    return 0;
}

SHELL_CMD_REGISTER(set, NULL, "Set value (0-100)", cmd_set_value);

2. 动态命令

/* 动态子命令生成 */
static void sensor_name_get(size_t idx, struct shell_static_entry *entry)
{
    static const char *sensors[] = {"temp", "humi", "press"};
    
    if (idx < ARRAY_SIZE(sensors)) {
        entry->syntax = sensors[idx];
        entry->handler = NULL;
        entry->subcmd = NULL;
        entry->help = "Sensor name";
    } else {
        entry->syntax = NULL;
    }
}

SHELL_DYNAMIC_CMD_CREATE(dsub_sensor, sensor_name_get);

3. 命令执行回调

/* 命令执行前回调 */
static int shell_cmd_pre_exec(const struct shell *sh)
{
    shell_print(sh, "Executing command...");
    return 0;
}

/* 命令执行后回调 */
static void shell_cmd_post_exec(const struct shell *sh)
{
    shell_print(sh, "Command completed");
}

4. Shell 状态保存

/* 保存 Shell 状态 */
static void save_shell_state(const struct shell *sh)
{
    /* 可以保存到 Settings 或 NVS */
}

/* 恢复 Shell 状态 */
static void restore_shell_state(const struct shell *sh)
{
    /* 从 Settings 或 NVS 恢复 */
}

内置命令

1. 系统命令

# 查看内核信息
uart:~$ kernel version
# 查看线程
uart:~$ kernel threads
# 查看设备列表
uart:~$ device list

2. 日志命令

# 查看日志
uart:~$ log show
# 设置日志级别
uart:~$ log level set 3
# 启用模块日志
uart:~$ log enable app

3. 内存命令

# 查看内存状态
uart:~$ kernel memmap
# 内存池状态
uart:~$ kernel mem_pool

4. Flash 命令

# Flash 操作(需启用)
uart:~$ flash read <addr> <size>
uart:~$ flash write <addr> <data>
uart:~$ flash erase <addr> <size>

完整示例

#include <zephyr/kernel.h>
#include <zephyr/shell/shell.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>

#define LED0_NODE DT_ALIAS(led0)
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

/* LED ON 命令 */
static int cmd_led_on(const struct shell *sh, size_t argc, char **argv)
{
    gpio_pin_set_dt(&led, 1);
    shell_print(sh, "LED is ON");
    return 0;
}

/* LED OFF 命令 */
static int cmd_led_off(const struct shell *sh, size_t argc, char **argv)
{
    gpio_pin_set_dt(&led, 0);
    shell_print(sh, "LED is OFF");
    return 0;
}

/* LED 闪烁命令 */
static int cmd_led_blink(const struct shell *sh, size_t argc, char **argv)
{
    if (argc < 2) {
        shell_error(sh, "Usage: led blink <interval_ms>");
        return -EINVAL;
    }
    
    int interval = atoi(argv[1]);
    shell_print(sh, "LED blinking at %d ms interval", interval);
    
    for (int i = 0; i < 10; i++) {
        gpio_pin_set_dt(&led, 1);
        k_msleep(interval);
        gpio_pin_set_dt(&led, 0);
        k_msleep(interval);
    }
    
    shell_print(sh, "Blink complete");
    return 0;
}

/* LED 状态命令 */
static int cmd_led_status(const struct shell *sh, size_t argc, char **argv)
{
    int state = gpio_pin_get_dt(&led);
    shell_print(sh, "LED is %s", state ? "ON" : "OFF");
    return 0;
}

/* LED 子命令 */
SHELL_STATIC_SUBCMD_SET_CREATE(sub_led,
    SHELL_CMD(on, NULL, "Turn LED on", cmd_led_on),
    SHELL_CMD(off, NULL, "Turn LED off", cmd_led_off),
    SHELL_CMD(blink, NULL, "Blink LED", cmd_led_blink),
    SHELL_CMD(status, NULL, "Show LED status", cmd_led_status),
    SHELL_SUBCMD_SET_END
);

SHELL_CMD_REGISTER(led, &sub_led, "LED control commands", NULL);

/* 设备信息命令 */
static int cmd_dev_info(const struct shell *sh, size_t argc, char **argv)
{
    shell_print(sh, "Device: nRF54L15 DK");
    shell_print(sh, "Board: %s", CONFIG_BOARD);
    shell_print(sh, "Kernel: %s", KERNEL_VERSION_STRING);
    return 0;
}

SHELL_CMD_REGISTER(dev, NULL, "Show device info", cmd_dev_info);

/* 主函数 */
int main(void)
{
    /* 初始化 LED */
    gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
    
    printk("Shell demo started\n");
    printk("Type 'help' for available commands\n");
    
    return 0;
}

nRF Connect SDK 特定

1. nRF Shell 模块

# prj.conf
CONFIG_NRF_SHELL=y
CONFIG_NRF_SHELL_BLE=y

2. Nordic 特定命令

# BLE 命令
uart:~$ ble addr
uart:~$ ble advertise start
uart:~$ ble scan start

# DFU 命令
uart:~$ dfu dm.list
uart:~$ dfu mcumgr

调试技巧

1. Shell 调试

# 启用 Shell 调试日志
CONFIG_SHELL_LOG_LEVEL_DBG=y

2. 命令追踪

/* 启用命令追踪 */
SHELL_CMD_DEFINE(traced_cmd, NULL, "Traced command", cmd_handler);

最佳实践

1. 命令命名

2. 错误处理

static int cmd_safe(const struct shell *sh, size_t argc, char **argv)
{
    int ret = do_something();
    if (ret < 0) {
        shell_error(sh, "Operation failed: %d", ret);
        return ret;
    }
    
    shell_print(sh, "Success");
    return 0;
}

3. 帮助信息

static int cmd_help(const struct shell *sh, size_t argc, char **argv)
{
    shell_print(sh, "Usage: mycommand <option> [value]");
    shell_print(sh, "Options:");
    shell_print(sh, "  -a: Option A");
    shell_print(sh, "  -b: Option B");
    return 0;
}

参考资料


学习日期: 2026-03-21 笔记编号: #51 作者: 小白 🤖