资料
这是我的单片机shell开源地址单片机shell(xcmd)
计划
- 基本功能实现手把手教你写单片机shell (一、基本功能实现)
- 控制光标移动
- 实现快捷键功能
- 实现历史记录
- 实现TAB自动补全
- 添加彩色字符串
- 添加常用扩展指令
简介
今天开始我们开始一步一步编写一个适合单片机运行的命令行工具,其功能类似linux上的超级终端。为了快速测试验证,我们使用的平台是arduino uno+putty,后续再尝试移植到其他平台。
实现
1. 测试putty串口行为
我们先来看一下串口终端软件,以putty为例。我们先编写第一个测试程序
void setup() {
Serial.begin(115200);
}
void loop() {
if(Serial.available())
{
char rcv = Serial.read();
Serial.print("rcv:");
Serial.println(rcv);
}
}
可以看到再putty中当我们输入字符时会自动通过串口将是数据发送到单片机中,并且,显示的内容为单片机发过来的数据。总结
- putty中按下一个按键就会发送给单片机一个对应的字符
- putty并不会显示自己发送了什么字符,显示的是自己接收到的字符
2. 实现putty串口回显
修改程序,完成从putty接受字符并把字符打印到putty上
void setup() {
Serial.begin(115200);
}
void loop() {
if(Serial.available())
{
char rcv = Serial.read();
Serial.print(rcv);
}
}
3. 实现从putty中读取字符串
接下来从putty接受字符并把字符打印到putty上,并且当接收到回车换行时把接收到的字符组织成字符串,回车后打印这句字符串。
为了方便后续打印我们自定义实现printf函数
void my_print(char* fmt, ...)
{
char buf[128] = {0};
va_list ap;
va_start(ap, fmt);
vsnprintf(buf,128,fmt, ap);
Serial.print(buf);
}
通过putty发送字符并以回车换行为结束,将获取到的字符组织成字符串。
uint8_t get_line(char *line, uint8_t maxLen)
{
char rcv_char;
static uint8_t count = 0;
if (Serial.available())
{
rcv_char = Serial.read();
if (count >= maxLen)
{
count = 0;
return 1;
}
line[count] = rcv_char;
switch (rcv_char)
{
case 0x08:
case 0x7F:
{
if (count > 0)
{
count--;
}
}
break;
case '\r':
case '\n':
{
line[count] = '\0';
count = 0;
return 1;
}
break;
default:
count++;
}
Serial.print(rcv_char);
}
return 0;
}
...详细代码在文末
void loop()
{
if(get_line(g_line_buf, LINE_BUF_MAX_LEN))
{
if(strlen(g_line_buf))
my_print("\r\ncmd is:\"%s\"\r\n", g_line_buf);
my_print("\r\n->");
}
}
4. 分割字符串获取输入的参数
接下来我们需要将获得到的字符串按照空格分隔成多个字符串以便命令函数调用。
static int get_param(char* msg, char*delim, char* get[], int max_num)
{
int i,ret;
char *ptr = NULL;
ptr = strtok(msg, delim);
for(i=0; ptr!=NULL &&i<max_num; i++)
{
get[i] = ptr;
ptr = strtok(NULL, delim);
}
ret = i;
return ret;
}
...详细代码在文末
void loop()
{
if(get_line(g_line_buf, LINE_BUF_MAX_LEN))
{
if(strlen(g_line_buf))
{
char *argv[16];
int argc = get_param(g_line_buf, " ", argv, 16);
int i = 0;
my_print("\r\nargc=%d ", argc);
my_print("argv=[");
for(i=0; i<(argc-1); i++)
{
my_print("%s,", argv[i]);
}
my_print("%s]\r\n", argv[i]);
}
my_print("\r\n->");
}
}
5. 定义命令函数以及命令调用过程
我们仿照main函数定义int main(int argc, char** argv) 我们的命令函数
typedef int(*cmd_func_t)(int argc, char**argv);
定义一个结构体用来描述命令
typedef struct
{
char* name;
cmd_func_t func;
char* help;
}cmd_t;
定义两个命令函数
int cmd_test(int argc, char**argv)
{
int i = 0;
my_print("argc=%d ", argc);
my_print("argv=[");
for(i=0; i<(argc-1); i++)
{
my_print("%s,", argv[i]);
}
my_print("%s]\r\n", argv[i]);
}
int cmd_help(int argc, char**argv)
{
for(int i=0; i<g_num_cmd; i++)
{
my_print("%-10s %s\r\n", g_my_cmds[i].name, g_my_cmds[i].help);
}
}
定义一个命令结构体数组用来存放自定义命令
cmd_t g_my_cmds[] =
{
{"test", cmd_test, "Show all params"},
{"help", cmd_help, "Show this list"}
};
int g_num_cmd = sizeof(g_my_cmds)/sizeof(cmd_t);
通过步骤3中解析的字符串来循环查找与命令名匹配的命令函数,执行命令函数并传入参数。
for(int i=0; i<g_num_cmd; i++)
{
if(strcmp(argv[0], g_my_cmds[i].name) == 0)
{
g_my_cmds[i].func(argc, argv);
}
}
6. 总结
基本上到这里我们的单片机shell的基本功能已经实现,可以接收来自putty的输入,并解析输入的字符串运行相关的自定义命令。接下来我们将进入下一阶段,来实现一个操作更加友好的单片机shell。
最后贴上完整的代码
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#define LINE_BUF_MAX_LEN (64)
char g_line_buf[LINE_BUF_MAX_LEN+1] = {0};
typedef int(*cmd_func_t)(int argc, char**argv);
typedef struct
{
char* name;
cmd_func_t func;
char* help;
}cmd_t;
int cmd_test(int argc, char**argv);
int cmd_help(int argc, char**argv);
cmd_t g_my_cmds[] =
{
{"test", cmd_test, "Show all params"},
{"help", cmd_help, "Show this list"}
};
int g_num_cmd = sizeof(g_my_cmds)/sizeof(cmd_t);
int cmd_test(int argc, char**argv)
{
int i = 0;
my_print("argc=%d ", argc);
my_print("argv=[");
for(i=0; i<(argc-1); i++)
{
my_print("%s,", argv[i]);
}
my_print("%s]\r\n", argv[i]);
}
int cmd_help(int argc, char**argv)
{
for(int i=0; i<g_num_cmd; i++)
{
my_print("%-10s %s\r\n", g_my_cmds[i].name, g_my_cmds[i].help);
}
}
void my_print(char* fmt, ...)
{
char buf[128] = {0};
va_list ap;
va_start(ap, fmt);
vsnprintf(buf,128,fmt, ap);
Serial.print(buf);
}
static int get_param(char* msg, char*delim, char* get[], int max_num)
{
int i,ret;
char *ptr = NULL;
ptr = strtok(msg, delim);
for(i=0; ptr!=NULL &&i<max_num; i++)
{
get[i] = ptr;
ptr = strtok(NULL, delim);
}
ret = i;
return ret;
}
uint8_t get_line(char *line, uint8_t maxLen)
{
char rcv_char;
static uint8_t count = 0;
if (Serial.available())
{
rcv_char = Serial.read();
if (count >= maxLen)
{
count = 0;
return 1;
}
line[count] = rcv_char;
switch (rcv_char)
{
case 0x08:
case 0x7F:
{
if (count > 0)
{
count--;
}
}
break;
case '\r':
case '\n':
{
line[count] = '\0';
count = 0;
return 1;
}
break;
default:
count++;
}
Serial.print(rcv_char);
}
return 0;
}
int match_cmd(int argc, char**argv)
{
int i = 0;
for(int i=0; i<g_num_cmd; i++)
{
if(strcmp(argv[0], g_my_cmds[i].name) == 0)
{
g_my_cmds[i].func(argc, argv);
}
}
if(i == g_num_cmd)
{
my_print("cmd \"%s\" does not exist!!\r\n");
}
}
void setup()
{
Serial.begin(115200);
}
void loop()
{
if(get_line(g_line_buf, LINE_BUF_MAX_LEN))
{
my_print("\r\n");
if(strlen(g_line_buf))
{
char *argv[16];
int argc = get_param(g_line_buf, " ", argv, 16);
match_cmd(argc, argv);
}
my_print("->");
}
}
|