finsh是RT-Thread的命令行外壳(shell),提供一套供用户在命令行的操作接口,主要用于调试、查看系统信息。在大部分嵌入式系统中,一般开发调试都使用硬件调试器和printf日志打印,在有些情况下,这两种方式并不是那么好用。比如对于RT-Thread这个多线程系统,我们想知道某个时刻系统中的线程运行状态、手动控制系统状态。如果有一个shell,就可以输入命令,直接相应的函数执行获得需要的信息,或者控制程序的行为。这无疑会十分方便。

finsh支持两种模式:

1. C语言解释器模式, 为行文方便称之为c-style;

2. 传统命令行模式,此模式又称为msh(module shell)。C语言表达式解释模式下, finsh能够解析执行大部分C语言的表达式,并使用类似C语言的函数调用方式访问系统中的函数及全局变量,此外它也能够通过命令行方式创建变量。在msh模式下,finsh运行方式类似于dos/bash等传统shell。

大致工作流程

一、finsh组件初始化函数finsh_system_init(),并且添加了INIT_COMPONENT_EXPORT(finsh_system_init),支持组件初始化;

这个函数会初始化finsh组件,包括一些finsh变量以及相关数据结构。

然后它会创建一个线程,代码如下:

result = rt_thread_init(&finsh_thread,
"tshell",
finsh_thread_entry, RT_NULL,
&finsh_thread_stack[], sizeof(finsh_thread_stack),
FINSH_THREAD_PRIORITY, ); if (result == RT_EOK)
rt_thread_startup(&finsh_thread);

可以看到,线程函数是finsh_thread_entry,在下一节中我们将分析它具体工作流程。

二、void finsh_set_device(const char* device_name)函数为finsh设置终端设备,在stm32中主要设置串口设备为终端。该函数一般放在组件初始化函数rt_component_init()后面,因为要先完成finsh组件初始化才能设置终端设备。

void finsh_set_device(const char* device_name)
{
rt_device_t dev = RT_NULL; RT_ASSERT(shell != RT_NULL);
dev = rt_device_find(device_name);
if (dev == RT_NULL)
{
rt_kprintf("finsh: can not find device: %s\n", device_name);
return;
} /* check whether it's a same device */
if (dev == shell->device) return;
/* open this device and set the new device in finsh shell */
if (rt_device_open(dev, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX |\
RT_DEVICE_FLAG_STREAM) == RT_EOK)
{
if (shell->device != RT_NULL)
{
/* close old finsh device */
rt_device_close(shell->device);
rt_device_set_rx_indicate(shell->device, RT_NULL);
} shell->device = dev;
rt_device_set_rx_indicate(dev, finsh_rx_ind);
}
}

这个函数为finsh组件设置使用的串口,从这个函数中我们可以总结出,如何使用串口设备。

  1. 调用rt_device_find使用设备的字符串名字查找设备,得到设备数据结构指针
  2. 调用rt_devcie_open打开设备,open_flag为RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX |RT_DEVICE_FLAG_STREAM

* 对finsh来说,还使用了rt_device_set_rx_indicate函数设置了一个回调函数finsh_rx_ind,它的作用我们后面会讨论

到这里设备就被打开了。

在serial.c中rt_hw_serial_isr()中有:

/* invoke callback */
if (serial->parent.rx_indicate != RT_NULL)
{
rt_size_t rx_length; /* get rx length */
level = rt_hw_interrupt_disable();
rx_length = (rx_fifo->put_index >= rx_fifo->get_index)? (rx_fifo->put_index - rx_fifo->get_index):
(serial->config.bufsz - (rx_fifo->get_index - rx_fifo->put_index));
rt_hw_interrupt_enable(level); serial->parent.rx_indicate(&serial->parent, rx_length);
}

上面计算得到rx_length,然后触发回调函数,也就是前面的finsh_rx_ind函数,即实际执行的是fins_rx_ind(device, rx_length)。

在shell.c中fins_rx_ind源码为:

static rt_err_t finsh_rx_ind(rt_device_t dev, rt_size_t size)
{
RT_ASSERT(shell != RT_NULL); /* release semaphore to let finsh thread rx data */
rt_sem_release(&shell->rx_sem); return RT_EOK;
}

这个函数里只是简单的释放信号量。也就是说,当串口硬件上接收到一个字节,就会调用finsh_rx_ind函数来释放一个信号量。

三、finsh线程函数的工作流程概述

void finsh_thread_entry(void* parameter)
{
char ch; /* normal is echo mode */
shell->echo_mode = ; #ifndef FINSH_USING_MSH_ONLY
finsh_init(&shell->parser);
#endif
rt_kprintf(FINSH_PROMPT); /* set console device as shell device */
if (shell->device == RT_NULL)
{
#ifdef RT_USING_CONSOLE
shell->device = rt_console_get_device();
RT_ASSERT(shell->device);
rt_device_set_rx_indicate(shell->device, finsh_rx_ind);
rt_device_open(shell->device, (RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM | RT_DEVICE_FLAG_INT_RX));
#else
RT_ASSERT(shell->device);
#endif
} while ()
{
/* wait receive */
if (rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER) != RT_EOK) continue; /* read one character from device */
while (rt_device_read(shell->device, , &ch, ) == )
{
/*
* handle control key
* up key : 0x1b 0x5b 0x41
* down key: 0x1b 0x5b 0x42
* right key:0x1b 0x5b 0x43
* left key: 0x1b 0x5b 0x44
*/
if (ch == 0x1b)
{
shell->stat = WAIT_SPEC_KEY;
continue;
}
else if (shell->stat == WAIT_SPEC_KEY)
{
if (ch == 0x5b)
{
shell->stat = WAIT_FUNC_KEY;
continue;
} shell->stat = WAIT_NORMAL;
}
else if (shell->stat == WAIT_FUNC_KEY)
{
shell->stat = WAIT_NORMAL; if (ch == 0x41) /* up key */
{
#ifdef FINSH_USING_HISTORY
/* prev history */
if (shell->current_history > )
shell->current_history --;
else
{
shell->current_history = ;
continue;
} /* copy the history command */
memcpy(shell->line, &shell->cmd_history[shell->current_history][],
FINSH_CMD_SIZE);
shell->line_curpos = shell->line_position = strlen(shell->line);
shell_handle_history(shell);
#endif
continue;
}
else if (ch == 0x42) /* down key */
{
#ifdef FINSH_USING_HISTORY
/* next history */
if (shell->current_history < shell->history_count - )
shell->current_history ++;
else
{
/* set to the end of history */
if (shell->history_count != )
shell->current_history = shell->history_count - ;
else
continue;
} memcpy(shell->line, &shell->cmd_history[shell->current_history][],
FINSH_CMD_SIZE);
shell->line_curpos = shell->line_position = strlen(shell->line);
shell_handle_history(shell);
#endif
continue;
}
else if (ch == 0x44) /* left key */
{
if (shell->line_curpos)
{
rt_kprintf("\b");
shell->line_curpos --;
} continue;
}
else if (ch == 0x43) /* right key */
{
if (shell->line_curpos < shell->line_position)
{
rt_kprintf("%c", shell->line[shell->line_curpos]);
shell->line_curpos ++;
} continue;
} } /* handle CR key */
if (ch == '\r')
{
char next; if (rt_device_read(shell->device, , &next, ) == )
ch = next;
else ch = '\r';
}
/* handle tab key */
else if (ch == '\t')
{
int i;
/* move the cursor to the beginning of line */
for (i = ; i < shell->line_curpos; i++)
rt_kprintf("\b"); /* auto complete */
shell_auto_complete(&shell->line[]);
/* re-calculate position */
shell->line_curpos = shell->line_position = strlen(shell->line); continue;
}
/* handle backspace key */
else if (ch == 0x7f || ch == 0x08)
{
/* note that shell->line_curpos >= 0 */
if (shell->line_curpos == )
continue; shell->line_position--;
shell->line_curpos--; if (shell->line_position > shell->line_curpos)
{
int i; rt_memmove(&shell->line[shell->line_curpos],
&shell->line[shell->line_curpos + ],
shell->line_position - shell->line_curpos);
shell->line[shell->line_position] = ; rt_kprintf("\b%s \b", &shell->line[shell->line_curpos]); /* move the cursor to the origin position */
for (i = shell->line_curpos; i <= shell->line_position; i++)
rt_kprintf("\b");
}
else
{
rt_kprintf("\b \b");
shell->line[shell->line_position] = ;
} continue;
} /* handle end of line, break */
if (ch == '\r' || ch == '\n')
{
#ifdef FINSH_USING_HISTORY
shell_push_history(shell);
#endif #ifdef FINSH_USING_MSH
if (msh_is_used() == RT_TRUE)
{
rt_kprintf("\n");
msh_exec(shell->line, shell->line_position);
}
else
#endif
{
#ifndef FINSH_USING_MSH_ONLY
/* add ';' and run the command line */
shell->line[shell->line_position] = ';'; if (shell->line_position != ) finsh_run_line(&shell->parser, shell->line);
else rt_kprintf("\n");
#endif
} rt_kprintf(FINSH_PROMPT);
memset(shell->line, , sizeof(shell->line));
shell->line_curpos = shell->line_position = ;
break;
} /* it's a large line, discard it */
if (shell->line_position >= FINSH_CMD_SIZE)
shell->line_position = ; /* normal character */
if (shell->line_curpos < shell->line_position)
{
int i; rt_memmove(&shell->line[shell->line_curpos + ],
&shell->line[shell->line_curpos],
shell->line_position - shell->line_curpos);
shell->line[shell->line_curpos] = ch;
if (shell->echo_mode)
rt_kprintf("%s", &shell->line[shell->line_curpos]); /* move the cursor to new position */
for (i = shell->line_curpos; i < shell->line_position; i++)
rt_kprintf("\b");
}
else
{
shell->line[shell->line_position] = ch;
if (shell->echo_mode)
rt_kprintf("%c", ch);
} ch = ;
shell->line_position ++;
shell->line_curpos++;
if (shell->line_position >= )
{
/* clear command line */
shell->line_position = ;
shell->line_curpos = ;
}
} /* end of device read */
}
}

函数主体依然是一个while(1)循环,这是显然的,因为finsh要不停的监听终端上输入。

 if (rt_sem_take(&shell->rx_sem, RT_WAITING_FOREVER) != RT_EOK) continue;

即,如果串口上没有收到任何数据,并且串口缓冲区中也无数据,即shell→rx_sem信号量的值为0,那么这个函数会使finsh线程休眠,RTT内核会执行其他线程。

当串口收到数据,串口终端调用回调函数finsh_rx_ind函数来释放信号量,这会唤醒finsh线程,rt_sem_take函数会执行完毕,继续执行接下来的代码。

接下来的代码调用rt_device_read函数从串口数据缓冲池中读取一个字节。

然后判断所读取的到这个字节(判断上下左右四个按键所代表的字节)。

(1) 如果是'\r',即表示用户按下了回车键,再调用rt_device_read函数来读取一个字节,如果读到,则这将更新读到的字节,一般情况下,这个函数会返回0,即没有读到新的字节。

(2) 如果是'\t',即表示用户按下了TAB键,则调用finsh_auto_complete函数,这个函数做自动补全操作,也就是根据当前已输入的字符串,从finsh内部已注册的函数/变量中查找匹配字符串,如果找到则会在终端上自动补全。

(3) 如果是0x7f或者0x08 说明:查ascii码表可知,0x08 表示按下了backspace键,【0x7f表示按下了DEL键,这个不对劲,如何知道当我们按下了键盘按键时,串口都收到了什么数据呢?】 这表示用户期望删除已经输入的字符串,根据测试结果,发送”\0x08 \0x08”,可以实现退格。

(4) 如果收到了'\r'或者'\n',则表示用户按下了回车,希望处理这个命令,那么finsh_run_line函数被执行,这个函数会从从finsh已注册的函数/变量中匹配当前从终端里获取的字符串,如果匹配到,则执行对应的函数(若字符串为函数名)或者打印变量的值(若字符串为已变量)。

(5) 回显字符,也就是将刚才从串口接收到终端发送的字符发送到终端软件上显示出来。这就是说,我们在终端软件上输入字符,并且可以看到我们输入的字符,实际上是板子上的串口重新发回来显示的。在上面finsh的线程代码中,rt_device_write函数是在rt_kprintf中调用的。

然后回到(1),重复这个过程。

RT-thread finsh组件工作流程的更多相关文章

  1. Ribbon的主要组件与工作流程

    一:Ribbon是什么? Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起.Ribbon客户端组件提供一系列完善的配置项如连接 ...

  2. RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组件介绍

    RDIFramework.NET ━ .NET快速信息化系统开发框架 工作流程组件介绍 RDIFramework.NET,基于.NET的快速信息化系统开发.整合框架,给用户和开发者最佳的.Net框架部 ...

  3. RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组件Web业务平台

    RDIFramework.NET ━ .NET快速信息化系统开发框架  工作流程组件Web业务平台 接前两篇: RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组件介 ...

  4. RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组件WinForm业务平台

    RDIFramework.NET ━ .NET快速信息化系统开发框架 工作流程组件WinForm业务平台 接上篇: RDIFramework.NET ━ .NET快速信息化系统开发框架 ━ 工作流程组 ...

  5. OpenStack各组件逻辑关系、通信部署关系及工作流程

    一. OpenStack组件之间的逻辑关系 OpenStack 是一个不断发展的系统,所以 OpenStack 的架构是演进的,举个例子: E 版本有5个组件  Compute 是 Nova:Imag ...

  6. RDIFramework.NET敏捷开发框架 ━ 工作流程组件介绍

    RDIFramework.NET,基于.NET的快速信息化系统敏捷开发.整合框架,给用户和开发者最佳的.Net框架部署方案. 1.RDIFramework.NET敏捷开发框架介绍 RDIFramewo ...

  7. RDIFramework.NET敏捷开发框架 ━ 工作流程组件Web业务平台

    接前两篇: RDIFramework.NET敏捷开发框架 ━ 工作流程组件介绍 RDIFramework.NET敏捷开发框架 ━ 工作流程组件WinForm业务平台 1.RDIFramework.NE ...

  8. STM32 移植 RT-Thread 标准版的 FinSH 组件

    一.移植准备 开发版STM32F10xC8T6 准备好移植RT-Thread的移植工程 没动手移植过RT-Thread的小伙伴,可以看RT-Thread移植到stm32 我这里是将控制台信息打印到串口 ...

  9. Storm 中什么是-acker,acker工作流程介绍

    概述 我们知道storm一个很重要的特性是它能够保证你发出的每条消息都会被完整处理, 完整处理的意思是指: 一个tuple被完全处理的意思是: 这个tuple以及由这个tuple所导致的所有的tupl ...

随机推荐

  1. Java 高级应用编程 第一章 工具类

    一.Java API Java API简介 1.API (Application Programming Interface) 应用程序接口 2.Java中的API,就是JDK提供的各种功能的Java ...

  2. 日志工具——slf4j

    一.概述 简单日记门面(simple logging Facade for java)SLF4J是为各种loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希望的lo ...

  3. LeetCode: 62. Unique Paths(Medium)

    1. 原题链接 https://leetcode.com/problems/unique-paths/description/ 2. 题目要求 给定一个m*n的棋盘,从左上角的格子开始移动,每次只能向 ...

  4. LeetCode: 56. Merge Intervals(Medium)

    1. 原题链接 https://leetcode.com/problems/merge-intervals/description/ 2. 题目要求 给定一个Interval对象集合,然后对重叠的区域 ...

  5. 使数据可供ArcGIS Server访问

    内容来自ESRI官方文档(点击访问),简单总结如下: 1 ArcGIS Server用于发布服务的数据必须存储在服务器可以访问的位置: 2 这样的位置有三种类型: 本地路径:将数据本地存储在每台 Ar ...

  6. kaldi HMM-GMM全部训练脚本分解

    目录 train_mono.sh train_deltas.sh train_lda_mllt.sh train_sat.sh train_mono.sh 单音素训练脚本: //初始化,[topo f ...

  7. C语言中动态内存的分配(malloc,realloc)

    动态内存分配:根据需要随时开辟,随时释放的内存分配方式.分配时机和释放时机完全由程序员决定,由于没有数据声明,这部分空间没有名字.无法像使用变量或数组那样通过变量名或数组名引用其中的数据,只能通过指针 ...

  8. UML建模语言入门 -- 静态图详解 类图 对象图 包图 静态图建模实战

    发现个好东西思维导图, 最近开始用MindManager整理博客 . 作者 :万境绝尘  转载请注明出处 : http://blog.csdn.net/shulianghan/article/deta ...

  9. encode 与 decode

    decode 将其它编码的字符串转换成unicode编码,例如:str1.decode("gb2312"),表示将gb2312编码的字符串转换成unicode编码 encode 将 ...

  10. java—连连看-实现消除

    实现消除 1.Chess.java package Linkup; /** * 棋子封装类 * * @author laixl * */ public class Chess { // 图片的 状态 ...