lettershell项目链接 https://github.com/NevermindZZT/letter-shell
更多精彩内容欢迎关注微信公众号:码农练功房
letter shell
是一个C语言编写的,可以嵌入在程序中的嵌入式shell。主要面向嵌入式设备,以C语言函数为运行单位,可以通过命令行调用,运行程序中的函数。
letter-shell
为调试单片机程序提供了极大的便利。它使得开发者能够在不连接调试器的情况下通过命令行界面与单片机进行交互,这对于快速迭代和调试非常有用。
目录
物理结构
本文基于letter-shell
3.x版本。
.
├── demo
│ ├── esp-idf
│ ├── segger-rtt
│ ├── stm32-freertos
│ └── x86-gcc
├── doc
│ └── img
├── extensions
│ ├── cpp_support
│ ├── fs_support
│ ├── game
│ ├── log
│ ├── shell_enhance
│ └── telnet
├── LICENSE
├── README.md
├── src
│ ├── shell.c
│ ├── shell_cfg.h
│ ├── shell_cmd_list.c
│ ├── shell_companion.c
│ ├── shell_ext.c
│ ├── shell_ext.h
│ └── shell.h
└── tools
demo目录下提供了各个平台移植示例,使用者根据需要可以参考。
extensions目录下是一些拓展功能。
src目录下的文件为letter-shell
的核心源码。
STM32裸机移植
demo/stm32-freertos提供了基于freertos的移植示例。裸机移植与之类似,将src下的文件全部拷贝到MDK工程下,同时新建shell_port.h、shell_port.c文件,加入工程:
移植其实就是配置shell_cfg.h中定义的宏、针对具体平台实现shell读写函数,如果我们需要使用单片机的串口实现shell的功能:
// shell_port.c
#include "shell.h"
#include "shell_port.h"
#include "stm32f10x_usart.h"
/* 1. 创建shell对象,开辟shell缓冲区 */
Shell shell;
char shell_buffer[512];
/* 2. 自己实现shell写函数 */
signed short User_Shell_Write(char *data, unsigned short size)
{
for(int i = 0;i <size;++i)
{
USART_SendData(USART1,data[i]);
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
return size;
}
signed short User_Shell_Read(char *data, unsigned short size)
{
if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET)
{
return 0;
}
*data = USART_ReceiveData(USART1);
return 1;
}
/* 3. 编写初始化函数 */
void userShellInit(void)
{
//注册自己实现的写函数
shell.write = User_Shell_Write;
shell.read = User_Shell_Read;
//调用shell初始化函数
shellInit(&shell, shell_buffer, 512);
}
对应头文件如下:
// shell_port.h
#ifndef __SHELL_PORT_H__
#define __SHELL_PORT_H__
#include "shell.h"
extern Shell shell;
void userShellInit(void);
#endif
对于裸机环境,有两种建立shell任务的方式:
方式一为轮询方式,在主循环中调用shellTask
,需要使能SHELL_TASK_WHILE
宏:
int main(void)
{
SystemInit(); //配置系统时钟为 72M
SysTick_Configuration();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
USART1_Config(); //USART1 配置
LED_GPIO_Config();
userShellInit();
shellTask(&shell);
}
// shell.c
void shellTask(void *param)
{
Shell *shell = (Shell *)param;
char data;
#if SHELL_TASK_WHILE == 1
while(1)
{
#endif
if (shell->read && shell->read(&data, 1) == 1)
{
shellHandler(shell, data);
}
#if SHELL_TASK_WHILE == 1
}
#endif
}
方式二为中断方式,这种方式下不用注册shell->read
,但需要在中断中调用shellHandler
:
void USART1_IRQHandler(void) //串口1中断服务程序
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
u8 Res =USART_ReceiveData(USART1);
shellHandler(&shell,Res);
}
}
两种方式选择一种即可。两种方式内部其实都是调用了shellHandler,这个函数十分重要,是获取数据的入口。
使用MDK编译时,需要在keil的target option中增加–keep shellCommand*,防止定义的命令被优化掉。
对于命令定义先按下不表,后面文章我们会详细分析命令注册机制。
Linux平台移植
demo/x86-gcc下已经为我们完成了Linux平台下的移植,这里我们比较Linux平台下的移植和单片机下的移植有何差异。
这里仅截取Linux平台下,涉及到基础功能相关的移植代码:
// shell_port.c
unsigned short userShellWrite(char *data, unsigned short len)
{
unsigned short length = len;
while (length--)
{
putchar(*data++);
}
return len;
}
unsigned short userShellRead(char *data, unsigned short len)
{
unsigned short length = len;
while (length--)
{
*data++ = getchar();
}
return len;
}
void userShellInit(void)
{
// ......
shell.write = userShellWrite;
shell.read = userShellRead;
// ......
shellInit(&shell, shellBuffer, 512);
// ......
}
// main.c
int main(void)
{
signal(SIGINT, signalHandler);
system("stty -echo");
system("stty -icanon");
userShellInit();
shellTask(&shell);
return 0;
}
和STM32相比,注册的write、read函数不同了。在Linux平台下底层读写依赖于getchar、putchar,而对于STM32依赖于固件库中的串口读写。
总结
- letter-shell通过write、read函数注册的方式确实提高了它的灵活性,使得 letter-shell 可以适应不同的平台和环境,无论是单片机还是 Linux 系统。这种方式允许开发者根据自己的需求选择合适的输入输出机制,这种方式不仅提高了可移植性,还增强了 letter-shell 的实用性。