Arduboy应用之abshell
最近在看 Arduboy 中一些应用的源码,发现了一个叫 abshell 的应用,用来实现通过串口与arduboy进行命令行交互的,这个应用可以很方便的能够通过串口命令行在arduboy的屏幕上面进行图形绘制,看了下源码,发现里面包含了一个叫ntshell 的开源库。今天就来看下 abshell 是如何实现的,以及 ntshell 这个开源库该如何应用。
abshell 使用
github源码链接
下载源码,在 arduino IDE 中打开工程文件,编译烧写到 arduboy 中,就可以实现下面的交互了。
abshell 帮助
串口波特率设置为 115200
,直接在串口命令行中敲入 help
,就可以打印出所有支持的命令。
Arduboy>help
help :show help
info :show info
print :print string at the pen position
clear :clear display
circle :draw a circle
fcircle :draw a filled circle
line :draw a line
rect :draw a rectangle
frect :draw a filled rectangle
rrect :draw a rounded rectangle
frrect :draw a filled rounded rectangle
tri :draw a triangle
ftri :draw a filled triangle
moveto :set the pen location
tsize :set text size scale factor
lineto :draw a line from the pen position
color :set pen color
keystat :read the status of buttons
pixels :set pixels on horizontal line
tone :play a beep tone
bitmap :set bitmap destination area
.x :send raw data in hex string
Arduboy>
abshell 实现
#include "globals.h"
#include "ntshell.h"
extern "C" {
#include "ntshell_arduino.h"
}
#include "usrcmd_arduino.h"
#define PROMPTSTR "Arduboy>"
// 定义一个 ntshell 对象
static ntshell_t ntshell;
// 读写函数实现 这里直接调用串口的读写函数
static int func_read(char *buf, int cnt, void *extobj) {
if (Serial.available())
return Serial.readBytes(buf, cnt);
else
return 0;
}
static int func_write(const char *buf, int cnt, void *extobj) {
return Serial.write(buf, cnt);
}
// 回调函数 用于 ntshell 对象回调调用
static int func_callback(const char *text, void *extobj) {
#if 0
Serial.print("User input text:'");
Serial.print(text);
Serial.print("'\r\n");
#else
//直接调用 用户命令执行函数 usrcmd_execute
return usrcmd_execute(text);
#endif
}
void setup() {
arduboy.begin();
arduboy.setFrameRate(SCRN_FPS);
G_TXT_CURSOR(0, 48);
G_PRINT(F("open serial monitor !"));
arduboy.display();
Serial.begin(115200);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
arduboy.clear();
// 初始化 ntshell
ntshell_init(
&ntshell,
func_read,
func_write,
func_callback,
(void *)(&ntshell));
ntshell_set_prompt(&ntshell, PROMPTSTR);
Serial.println(F("Welcome to Arduboy.\r\n type 'help' for help."));
Serial.print(PROMPTSTR);
Serial.flush();
}
void loop() {
// 串口正常使用后,执行体 ntshell_execute_arduino
while(Serial.available())
ntshell_execute_arduino(&ntshell);
if (!(arduboy.nextFrame()))
return;
arduboy.display();
}
这里面主要学习到了 ntshell 的几个 API 函数。
-
ntshell_init
-
ntshell_set_prompt
-
ntshell_execute_arduino
除了以上的 API 之外,还需要注意了解一下 命令解析执行函数 usrcmd_execute 的具体实现,这个函数在 usrcmd_arduino.cpp 实现。
usrcmd_arduino.cpp
usrcmd_arduino.cpp
是 arduboy 的shell 命令解析的具体实现,通常来讲,一个 shell 命令的解析器大都是按照这样的逻辑来实现的,下面看一下具体的源码。
#include "ntopt.h"
#include "ntlibc.h"
#include <Arduino.h>
#include <avr/pgmspace.h>
#include "version.h"
#include "usrcmd_arduboy.h"
#define uart_puts Serial.print
void pgm_print(const char *p) {
char c;
while ((c = pgm_read_byte(p++)) != 0)
Serial.write(c);
}
// 定义一个函数指针
typedef int (*USRCMDFUNC)(int argc, char **argv);
static int usrcmd_ntopt_callback(int argc, char **argv, void *extobj);
static int usrcmd_help(int argc, char **argv);
static int usrcmd_info(int argc, char **argv);
const char cmd_0[] PROGMEM = "help";
const char desc_0[] PROGMEM = "show help";
const char cmd_1[] PROGMEM = "info";
const char desc_1[] PROGMEM = "show info";
// shell 命令结构体
typedef struct {
PROGMEM const char *cmd; // shell 命令名称
PROGMEM const char *desc; // shell 命令帮助信息
USRCMDFUNC func; // 函数调用
} cmd_table_t;
// 实例化 shell 命令数组 这里面大部分的 shell 命令的实现都是在 usrcmd_arduboy.cpp 中完成的
// 如果需要添加命令,可以在这个命令列表中进行添加,并针对相对应的命令编写回调函数
static const cmd_table_t cmdlist[] PROGMEM = {
{ cmd_0, desc_0, usrcmd_help },
{ cmd_1, desc_1, usrcmd_info },
{ cmd_10, desc_10, usrcmd_print },
{ cmd_11, desc_11, usrcmd_clear },
{ cmd_12, desc_12, usrcmd_circle },
{ cmd_13, desc_13, usrcmd_fcircle },
{ cmd_14, desc_14, usrcmd_line },
{ cmd_15, desc_15, usrcmd_rect },
{ cmd_16, desc_16, usrcmd_frect },
{ cmd_17, desc_17, usrcmd_rrect },
{ cmd_18, desc_18, usrcmd_frrect },
{ cmd_19, desc_19, usrcmd_tri },
{ cmd_20, desc_20, usrcmd_ftri },
{ cmd_21, desc_21, usrcmd_moveto },
{ cmd_22, desc_22, usrcmd_tsize },
{ cmd_23, desc_23, usrcmd_lineto },
{ cmd_24, desc_24, usrcmd_color },
{ cmd_25, desc_25, usrcmd_keystat },
{ cmd_26, desc_26, usrcmd_pixels },
{ cmd_30, desc_30, usrcmd_tone },
{ cmd_40, desc_40, usrcmd_bitmap },
{ cmd_41, desc_41, usrcmd_x },
};
int usrcmd_execute(const char *text)
{
// 回调函数 调用 usrcmd_ntopt_callback
return ntopt_parse(text, usrcmd_ntopt_callback, 0);
}
// shell 命令解析执行 调用 shell 命令相关的回调
static int usrcmd_ntopt_callback(int argc, char **argv, void *extobj)
{
if (argc == 0) {
return 0;
}
cmd_table_t *p;
char cmd[MAX_CMD_LEN];
p = (cmd_table_t *) &cmdlist[0];
for (int i = 0; i < sizeof(cmdlist) / sizeof(cmdlist[0]); i++) {
char *pcmd = pgm_read_word(&((*p).cmd));
USRCMDFUNC pfunc = pgm_read_word(&((*p).func));
strcpy_P(cmd, pcmd);
if (ntlibc_strcmp((const char *)argv[0], cmd) == 0) {
return (*pfunc)(argc, argv); //执行 shell 命令相关回调函数
}
p++;
}
uart_puts(F("Unknown command\r\n"));
Serial.flush();
return 0;
}
static int usrcmd_help(int argc, char **argv)
{
const cmd_table_t *p = &cmdlist[0];
for (int i = 0; i < sizeof(cmdlist) / sizeof(cmdlist[0]); i++) {
char *pcmd = pgm_read_word(&((*p).cmd));
char *pdesc = pgm_read_word(&((*p).desc));
pgm_print(pcmd);
uart_puts("\t:");
pgm_print(pdesc);
uart_puts("\r\n");
p++;
}
Serial.flush();
return 0;
}
static int usrcmd_info(int argc, char **argv)
{
if (argc != 2) {
uart_puts(F("info sys\r\n"));
uart_puts(F("info ver\r\n"));
return 0;
}
if (ntlibc_strcmp(argv[1], "sys") == 0) {
uart_puts(SYSINFOSTR);
return 0;
}
if (ntlibc_strcmp(argv[1], "ver") == 0) {
uart_puts(VERSIONSTR);
return 0;
}
uart_puts(F("Unknown sub command found\r\n"));
return -1;
}
ntshell 介绍
ntshell 实现了一个终端命令行交互的组件框架,除此之外还实现了一些特殊按键输入的处理,ntshell 的实现也比较简单,代码全部使用C语言编写,完全兼容 C89 标准。
如果要使用 ntshell ,我们只需要关系输入、输出以及命令处理部分的三个回调函数即可,这部分如何使用,可以直接参考 arduboy 上面的 abshell 的调用。
在使用 ntshell 过程中,没有对第三方库的依赖,也非常方便的移植。
目前,在 ntshell 官网上面可以下载到的最新的代码版本是 v0.3.1
。
官网介绍
ntshell 架构
ntshell 核心库代码说明
结合着 ntshell 的框架图,查看 ntshell-v0.3.1 的源码,源码结构如下。
如何在其他设备上使用 ntshell 开源组件
移植 ntshell 也非常方便,直接将 ntshell-v0.3.1 文件夹中的 lib库移植到自己的工程文件中即可,至于 ntshell 相关API 的调用范例,可以直接参考 arduboy 中的abshell 的具体实现,也可以参考 ntshell-v0.3.1 中提供的sample,这里面还提供了两款单片机中的移植实现,以及相关API的调用。
linux 下运行示例
这里提供一种在linux 测试运行 ntshell 的示例。