IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 手把手教你写单片机shell (一、基本功能实现) -> 正文阅读

[系统运维]手把手教你写单片机shell (一、基本功能实现)

资料

这是我的单片机shell开源地址单片机shell(xcmd)

计划

  1. 基本功能实现手把手教你写单片机shell (一、基本功能实现)
  2. 控制光标移动
  3. 实现快捷键功能
  4. 实现历史记录
  5. 实现TAB自动补全
  6. 添加彩色字符串
  7. 添加常用扩展指令

简介

今天开始我们开始一步一步编写一个适合单片机运行的命令行工具,其功能类似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'; /*添加字符串结束符,刚好可以去掉'\r'或者'\n'*/
				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)) /* 判断接受到的指令字符串是否为0 */
			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)) /* 判断接受到的指令字符串是否为0 */
		{
			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'; /*添加字符串结束符,刚好可以去掉'\r'或者'\n'*/
				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)) /* 判断接受到的指令字符串是否为0 */
		{
			char *argv[16];
			int argc = get_param(g_line_buf, " ", argv, 16);

			match_cmd(argc, argv);
			
		}
		my_print("->");
	}
}
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-10-03 17:25:02  更:2021-10-03 17:27:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 18:05:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码