单片机实现简易版shell的方法和原理

单片机实现简易版shell的方法和原理

Rt-thread 中有一个完整的finsh(shell )系统,使用串口做命令行输入输出.但是想要用这个炫酷的工具就必须要上rtthread系统,或者花大力气将其移植出来.于是我就自己写了一个类似于这样的插件.只需要把一对.c/.h文件加入到你的工程,就可以实现这个简易版的shell.

git: https://github.com/KimAlittleStar/ExternFunc
ExternFunc.c

#include "stdio.h"
#include "string.h"
#include "ExternFunc.h"
#include "stm32f4xx_hal.h"

#define MATCH_CASE_ENABLE     0             //函数调用名称大小写是否敏感 1表示敏感 0 表示不敏感

void show(int i);
void showcircle(char ch,int r);

static int ExternFunc_Find(char* funcname);
static void ExternFunc_list(void);
static void ExternFunc_SocReset(void);
static unsigned char matchString(const char* str1,const char* str2);

const CALLFUNCTIONTABLE functable[] =
{
    EXPOTRFUNC(LIST,ExternFunc_list, ,函数列表),
    EXPOTRFUNC(RST,ExternFunc_SocReset,,芯片软件复位),
    EXPOTRFUNC(circle,showcircle,%c %d,串口显示一个圆),
    EXPOTRFUNC(九九乘法表,show,%d,%d乘法表)
};
//EXPOTRFUNC( 函数别名命令行调用的名字 |真正的函数名 | 函数传参的格式字符串 |这个函数的简介)
void simplefunction(char* str,unsigned int sum,float dee,char ch)
{
    
    printf("接收到的字符串是:%s,\n\
接收到的字符是: %c \n\
接受到的数字是 %d\n\
接收到的小数是 %f __ \n ",str,ch,sum,dee);
}

void showcircle(char ch,int r)
{
    for(int i = 1; i<=(2*r); i++)
    {
        for(int j = 1; j<(2*r); j++)
        {
            if(((i-r)*(i-r)+(j-r)*(j-r))<=(r*r))
                printf("%c ",ch);
            else
                printf("%c ",' ');
        }
        printf("\n");
    }
}

void show(int i)
{
    for(int qq = 1;qq<= i;qq++)
    {
        for(int j = 1;j<=qq;j++)
        {
            printf("%dx%d=%2d  ",j,qq,j*qq);
        }
        printf("\n");
    }
}
//---------以上示例函数可删除----------------------



//----------------------------------------------------------真正的实现函数--------------
static int ExternFunc_Find(char* funcname)
{
    int size = sizeof(functable)/sizeof(functable[0]);
    for(int i = 0; i<size; i++)
    {
        if(matchString(funcname,functable[i].FuncName) == 0)
            return i;
    }
    return -1;
}


static void* args[7][100] = {0};
void ExternFunc_excute(char* str)
{
    char* ptemp;
    char ch;
    ptemp = strstr(str," ");
    if(ptemp == NULL)
    {
        ptemp = str+strlen(str);
        ch = *ptemp;
    }
    else
    {
        ch = '\0';
        *ptemp = '\0';
        ptemp++;
    }



    
    int loc = ExternFunc_Find(str); //寻找函数
    if(loc == -1)
    {
        printf("%s are not find\n the function list :\n",str);
        ExternFunc_list();
        return ;
    }

    if(ch != '\0')
        *ptemp = ch;
    int success = sscanf(ptemp,functable[loc].fmt,&args[0][1],&args[1][1],&args[2][1],&args[3][1],&args[4][1],&args[5][1]);
    
    //为兼容 可以输入字符串而做出的妥协
    int i = 0;
    ptemp = (char*)functable[loc].fmt;
    for(i = 0;i<7;i++)
    {
        if((ptemp=strstr(ptemp,"%")) !=NULL)
        {
            
            if(*(++ptemp) == 's')
                args[i][0] = &args[i][1];
            else if(*ptemp == 'f')
            {
                args[i][0] = args[i][1];
            }else               
                args[i][0] = args[i][1];
        }else break;
    }
    if(i!= success)
    {    
        printf("Err: 函数%s 参数应该为%d个,但只有%d\n",functable[loc].FuncName,i,success);
        return ;
    }
    //调用真正的函数    
    functable[loc].func(args[0][0],args[1][0],args[2][0],args[3][0],args[4][0],args[5][0],args[6][0]);
}


void ExternFunc_list(void)
{
    static char isfirstPrint = 0;
    
    int size = sizeof(functable)/sizeof(functable[0]);
        printf("QuickComplet:");
        for(int i = 0;i<size;i++)
        {
            printf("\"%s\"",functable[i].FuncName);
            if(i != (size-1))
                printf(",");
        }
    printf("\n\n*---------------------------------------------------------\n");
    for(int i = 0; i<size; i++)
    {
        printf(" |    %s(%s);%30s\n",functable[i].FuncName,functable[i].fmt,functable[i].Introduction);
        if(i != size-1)
            printf(" |--------------------------------------------------------\n");
    }
    printf("*---------------------------------------------------------\n");
}




static void ExternFunc_SocReset(void)
{
    
    __set_FAULTMASK(1);//关闭所有中断
    NVIC_SystemReset();//复位
}


static unsigned char matchString(const char* str1,const char* str2)
{
    char* ptemp1 = (char*) str1;
    char* ptemp2 = (char*) str2;
    while(*ptemp1 != '\0' || *ptemp2 != '\0')
    {
#if MATCH_CASE_ENABLE==0        
        if(((*ptemp1-*ptemp2) == ('A'-'a') || (*ptemp1-*ptemp2) == ('a'-'A'))&& 
            (*ptemp1>= 'A' && *ptemp1<= 'z' && *ptemp2>= 'A' && *ptemp2<= 'z'))
        {
            ptemp1++;
            ptemp2++;  
        }else if(*ptemp1 != *ptemp2) return 1;
#else
        if(*ptemp1 != *ptemp2) return 1;
#endif
            
        else 
        {
            ptemp1++;
            ptemp2++;            
        }
    }
    if(*ptemp1 == '\0'&& *ptemp2 == '\0')
        return 0;
    else
        return 0xFF;
}


ExternFunc.h

#ifndef EXTERNFUNC_H_INCLUDED
#define EXTERNFUNC_H_INCLUDED

#include "stdio.h"
#include "string.h"

typedef struct
{
    const char * FuncName;
    void *( (*func)(void* args,...));
    const char * fmt;
    const char * Introduction;
} CALLFUNCTIONTABLE;

#define EXPOTRFUNC(NAME,FUNC,FTM,INTRO) {#NAME,(void *(*)(void* args,...))FUNC,#FTM,#INTRO}

extern const CALLFUNCTIONTABLE functable[];

void ExternFunc_excute(char* str);


#endif // EXTERNFUNC_H_INCLUDED

里面内置了两个函数 一个LIST函数和 RST

LIST 指令是打印出当前所有的可以调用的函数信息

RST 是复位单片机(Cortex M0.3.4.7 系列可用) 主要原理:禁止所有中断 ,人为触发复位中断.

其他函数可以删除.

原理:

函数传参的值以第一个参数的地址基准,依次向后偏移,可变参数的值获取方式也是这么做的.那么我们只需要把相应的值摆列排放到指定位置即可.

实现的关键在于一个函数指针:

void *( (*func)(void* args,...));

强制转换成 上述指针函数后可以传入不定参数,以适应不同函数,不同个传参值 ,不同的传参类型.

我们知道函数指针是指向函数的,里面存放的是函数的跳转地址.那么使用函数指针就是把某个函数的跳转地址存进来,等到 PC寄存器读取到之后,自动跳转到指定函数.上面这个函数指针指向一个什么函数呢?指向返回值为空类型指针 , 传参为 (void * args ,…) 格式的函数.而 printf 的函数原型 int printf(const char* str , … ) ; 这个" … " 代表可变参数.所以我们才可以同时 printf 多个值,同时 还带有 "… "的函数有哪些呢: sprintf ,scanf ,sscanf. 可能 sscanf 和 sprintf 大家见得不多.这里给大家示例一下怎么使用.大家就知道是什么功能了

 char buff [100];
 char *string = "hello world";
 int a = 10;
 sprintf(buff,"a = %d string = %s",a,string);
 printf("%s",buff);  //buff: "a = 10 string = hello world"
 char ssbuff[100] = "%d %s";
 int b;
 char sstring[50];
 sscanf(ssbuff,"10 HelloWorld",&b,sstring);
 printf(" b = %d sstring = %s",b ,sstring); //b = 10 sstring = "HelloWorld"

实现原理参考文末跳转链接

根据这个原理.当我们把函数 void show(int a ,int b); 强制转换为 void* show(void* a,…) 只要我们在 调用show() 的时候 只传送两个参数即可.如果你传入了其他参数,那么会有影响嘛,会有,会影响到函数里面的其他的局部变量的值.但是我们做好局部变量值的初始化的话,就没关系啦,

怎么做到 对应的函数 传对应的值 ? 在functable 中需要输入 %c %d 就是对于输入格式的限定.同时在sscanf 中会返回转换一个int 表示成功转换数; 例如%d %c 正常的话会返回2 那么我只要检测 他返回的和我检测到的%号 的个数不匹配.那么我就报错,个数不匹配.

通过 使用 sscanf 将字符串转化为对应格式.灌入指定的函数中,就可以执行响应函数啦.

不过这么做有风险吗? 有 ,因为是强制类型转换,对于类型检查不严格(就没有类型检查) .可能会出现奇奇怪怪的现象. 同时 因为 float 是使用2进制的科学计数法在内存中存储. long long 类型和 double 是64 位宽,而我们的void* 是32位宽.所以 double 类型 long long 类型 float 类型 带有这三类的传参函数 和自定义的struct类型 参数值都会不正常.

printf 原理:http://www.cnblogs.com/ThatsMyTiger/p/6924462.html

猜你喜欢

转载自blog.csdn.net/qq_39575645/article/details/86473409