基于AS32X601使用shell命令行终端详解
一、引言
Letter shell是一个C语言编写的,可以嵌入在程序中的嵌入式shell,主要面向嵌入式设备。简单来说是一个命令行交互软件,可以读取用户输入的命令,找到并执行命令对应的函数。本文基于国科安芯AS32A601开发板,实现轻量化的shell。
二、文件概述
本项目集成了一个轻量级串口命令行 Shell,支持通过 USART0 与主机交互,以 printf 为统一输出通道。
-Shell 提供基础命令( help 、 ver 、 echo 、 led ),可按需扩展到 ADC、SPI 等外设。
目录与文件
shell.h :Shell 对外 API 与类型。
shell.c :Shell 核心实现(输入缓冲、命令解析、调度)。
shell_cmds.c :示例命令注册与实现。
print.c :将 printf 输出重定向到 USART0 。
main.c :Shell 初始化与主循环集成。
serial_cli.ps1 :Windows 交互脚本,便捷串口调试。
2.1 shell.c
#include "shell.h"
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
/* RX ring buffer */
static volatile uint8_t rx_buf[256];
static volatile uint16_t rx_head = 0; /* write index */
static volatile uint16_t rx_tail = 0; /* read index */
/* Line buffer */
static char line_buf[SHELL_MAX_LINE];
static uint16_t line_len = 0;
/* Command registry */
static const ShellCmd *cmd_table[8];
static int cmd_table_count[8];
static int table_used = 0;
void shell_init(void (*writer)(const char *buf, int len)) {
rx_head = rx_tail = 0;
line_len = 0;
table_used = 0;
(void)writer; /* output uses printf directly */
}
void shell_register(const ShellCmd *cmds, int count) {
__asm volatile("fence.i");
if (!cmds || count <= 0) return;
if (table_used < (int)(sizeof(cmd_table)/sizeof(cmd_table[0]))) {
cmd_table[table_used] = cmds;
cmd_table_count[table_used] = count;
table_used++;
}
}
void shell_input_byte(uint8_t b) {
uint16_t next = (uint16_t)((rx_head + 1) & 0xFF);
if (next == rx_tail) {
/* overflow, drop byte */
return;
}
rx_buf[rx_head] = b;
rx_head = next;
}
static int tokenize(char *line, char **argv, int max_args) {
int argc = 0;
char *p = line;
while (*p && argc < max_args) {
while (*p == ' ' || *p == '\t') p++;
if (!*p) break;
argv[argc++] = p;
while (*p && *p != ' ' && *p != '\t') p++;
if (!*p) break;
*p++ = '\0';
}
return argc;
}
static void print_prompt(void) {
printf("\r\n> ");
}
static int dispatch(int argc, char **argv) {
if (argc <= 0) return 0;
const char *name = argv[0];
__asm volatile("fence.i");
if (strcmp(name, "help") == 0) {
printf("Commands:\r\n");
for (int t = 0; t < table_used; ++t) {
for (int i = 0; i < cmd_table_count[t]; ++i) {
const ShellCmd *c = &cmd_table[t][i];
printf(" %s - %s\r\n", c->name, c->desc ? c->desc : "");
__asm volatile("fence.i");
}
}
return 0;
}
for (int t = 0; t < table_used; ++t) {
for (int i = 0; i < cmd_table_count[t]; ++i) {
const ShellCmd *c = &cmd_table[t][i];
if (strcmp(name, c->name) == 0) {
return c->handler(argc, argv);
}
}
}
printf("Unknown command: %s\r\n", name);
return -1;
}
void shell_poll(void) {
/* Read bytes from ring and build lines */
while (rx_tail != rx_head) {
uint8_t b = rx_buf[rx_tail];
rx_tail = (uint16_t)((rx_tail + 1) & 0xFF);
if (b == '\r') {
/* ignore CR */
continue;
}
if (b == '\n') {
/* complete line */
line_buf[line_len] = '\0';
char *argv[SHELL_MAX_ARGS];
int argc = tokenize(line_buf, argv, SHELL_MAX_ARGS);
if (argc > 0) {
(void)dispatch(argc, argv);
}
line_len = 0;
print_prompt();
continue;
}
if (b == '\b' || b == 0x7F) {
/* backspace */
if (line_len > 0) line_len--;
continue;
}
if (line_len < SHELL_MAX_LINE - 1) {
line_buf[line_len++] = (char)b;
} else {
/* truncate on overflow */
}
}
}
主要函数分析
1. 初始化函数
void shell_init(void (*writer)(const char *buf, int len));
-
初始化缓冲区指针
-
writer 参数当前未使用(直接使用 printf)
2. 命令注册函数
void shell_register(const ShellCmd *cmds, int count);
-
注册一组命令
-
fence.i 指令:RISC-V 内存屏障,确保指令缓存一致性
3. 字节输入处理
void shell_input_byte(uint8_t b);
-
从串口接收单个字节
-
存入环形缓冲区
-
处理缓冲区溢出(丢弃字节)
4. 主轮询函数
void shell_poll(void);
核心处理逻辑:
-
从环形缓冲区读取字节
-
处理特殊字符:
-
普通字符存入行缓冲区
-
行完成后,分词并调度执行
5. 分词函数
static int tokenize(char *line, char **argv, int max_args);
-
空格/制表符分割命令行
-
支持最大 SHELL_MAX_ARGS 个参数
-
原地修改字符串(添加 \0 终止符)
6. 命令分发
static int dispatch(int argc, char **argv);
-
内置 help 命令:显示所有注册命令
-
遍历所有命令表查找匹配命令
-
调用对应的 handler 函数
2.2 shell_cmds.c
用户可在该文件中定义函数,并注册到命令列表中
#include "shell.h"
#include "led.h"
#include <string.h>
#include <stdio.h>
static int cmd_ver(int argc, char **argv) {
(void)argc; (void)argv;
printf("AS32X601 usart_eflash shell v0.1\r\n");
return 0;
}
static int cmd_echo(int argc, char **argv) {
for (int i = 1; i < argc; ++i) {
printf("%s%s", argv[i], (i == argc - 1) ? "" : " ");
}
printf("\r\n");
return 0;
}
static int cmd_led(int argc, char **argv) {
if (argc < 3) {
printf("Usage: led <on|off|toggle> <1|2|3>\r\n");
return -1;
}
int idx = argv[2][0] - '0';
if (idx < 1 || idx > 3) {
printf("Invalid LED index: %s\r\n", argv[2]);
return -1;
}
int toggle = (strcmp(argv[1], "toggle") == 0);
int on = (strcmp(argv[1], "on") == 0);
int off = (strcmp(argv[1], "off") == 0);
if (!(toggle || on || off)) {
printf("Invalid action: %s\r\n", argv[1]);
return -1;
}
switch (idx) {
case 1:
if (toggle) LED1_TOGGLE(); else if (on) LED1_ON(); else if (off) LED1_OFF();
break;
case 2:
if (toggle) LED2_TOGGLE(); else if (on) LED2_ON(); else if (off) LED2_OFF();
break;
case 3:
if (toggle) LED3_TOGGLE(); else if (on) LED3_ON(); else if (off) LED3_OFF();
break;
default:
break;
}
printf("led %s %d\r\n", argv[1], idx);
return 0;
}
static const ShellCmd default_cmds[] = {
{"ver", "Show shell version", cmd_ver},
{"echo", "Echo back arguments", cmd_echo},
{"led", "Control LEDs: led <on|off|toggle> <1|2|3>", cmd_led},
};
void shell_cmds_init(void) {
shell_register(default_cmds, (int)(sizeof(default_cmds)/sizeof(default_cmds[0])));
}
void shell_info()
{
printf("\r\n");
printf("\r\n");
printf (" _ _ _ _ _ _ \r\n");
printf( "| | ___| |_| |_ ___ _ __ ___| |__ ___| | |\r\n");
printf("| | / _ \\ __| __/ _ \\ '__| / __| '_ \\ / _ \\ | |\r\n");
printf("| |__| __/ |_| || __/ | \\__ \\ | | | __/ | |\r\n");
printf("|_____\\___|\\__|\\__\\___|_| |___/_| |_|\\___|_|_|\r\n");
printf ("\r\n");
printf("\r\n");
printf("Version: 0.1\n");
printf("Board: AS32X601\n");
printf("Build: " __DATE__ " " __TIME__ "\n");
printf("\n");
}
-
版本信息命令
static int cmd_ver(int argc, char **argv)
-
显示固件版本信息
2.回显命令
static int cmd_echo(int argc, char **argv)
-
打印所有参数(argv[0] 是命令名本身)
-
正确处理参数间的空格
3. LED 控制命令
static int cmd_led(int argc, char **argv)
-
完整的参数验证:参数数量、范围、合法性
-
清晰的错误提示
-
执行反馈:操作成功后打印确认信息
4. 命令表定义
static const ShellCmd default_cmds
-
结构清晰:命令名、描述、处理函数
-
包含使用示例(led命令)
5. 初始化函数
void shell_cmds_init(void)
-
自动计算命令数量,避免硬编码
-
提供清晰的模块初始化接口
2.3 main.c部分流程
shell_init(NULL);
shell_cmds_init();
shell_info();
printf("AS32X601 shell ready\r\nType 'help' to list commands.\r\n> ");
while(1)
{
if(SET == USART_GetFlagStatus(USART0, USART_FLAG_RXFNE))
{
usart_data = USART_ReceiveData(USART0);
ClearCache();
/* feed incoming byte to shell */
shell_input_byte(usart_data);
}
/* process any pending input and run commands */
shell_poll();
}
输入路径:串口接收中断或轮询将字节喂给 shell_input_byte ,Shell维护环形缓冲与状态机。
解析执行:按行解析命令,匹配已注册的命令表或函数指针,执行对应处理例程。
主循环:周期性调用 shell_poll 以处理缓冲区中的数据与命令
三、开发板验证:
该项目实现通过串口分别控制led 1,2,3翻转和回显功能

更多推荐
所有评论(0)