【问题标题】:Microcontroller Serial Command Interpreter in C/C++; Ways to do it;C/C++ 中的微控制器串行命令解释器;方法;
【发布时间】:2011-04-02 12:25:26
【问题描述】:

我想解释一个命令字符串,它由微控制器(PIC16f877A,如果这有什么不同的话)通过串行接收。

字符串的格式非常简单明了: $AABBCCDDEE(2 个字符的 5 个“块”+'$' 共 11 个字符)其中: $AA= 命令的实际名称(可以是字母、数字,两者都是;必填); BB-EE= 参数(数字;可选);

我想用 C/C++ 编写代码。

我想我可以通过串行获取字符串,将其分解成块,切换 () {case} 并 memcmp 命令块 ($AA)。然后我可以有一个二叉决策树来使用 BB CC DD 和 EE 块。

我想知道这是否是正确的做法(这对我来说有点难看,肯定有一种不那么乏味的方法来做到这一点!)。

【问题讨论】:

    标签: c command serial-port interpreter microcontroller


    【解决方案1】:

    不要过度设计它!这并不意味着盲目地编码,但是一旦您设计了看起来可以完成工作的东西,您就可以开始实施它。实施将为您提供有关您的架构的反馈。

    例如,在编写您的 switch 案例时,您可能会发现自己重写的代码与您刚刚为前一个案例编写的代码非常相似。实际上写下一个算法会帮助你看到一些你没有想到的问题,或者一些你没有看到的简化。

    不要在第一次尝试时就瞄准最好的代码。目标是

    • 易于阅读
    • 易于调试

    采取一些小步骤。您不必一次完成整个事情。

    • 从串口获取字符串。看起来很容易,对吧?好吧,让我们先这样做,只是打印出命令。
    • 将命令与参数分开。
    • 提取参数。每个命令的提取是否相同?你能设计一个对每个命令都有效的数据结构吗?

    一旦你做对了,你就可以开始想一个更好的解决方案。

    【讨论】:

    • 谢谢!我正要这样做..开始盲目编码,即:D
    【解决方案2】:

    ASCII 接口根据定义是丑陋的。理想情况下,您有某种帧结构,也许您有,$ 表示帧之间的划分,您说它们的长度为 11 个字符。如果总是 11 那很好,如果只是有时更难,希望在开头有一个 $,在结尾有 0x0A 和/或 0x0D/0x0A (CR/LF)。通常我有一个代码模块,它只是从串行端口中提取字节并将它们放入(循环)缓冲区中。缓冲可以追溯到串行端口几乎没有板上没有缓冲区的日子,但即使在今天,尤其是微控制器,情况仍然如此。然后是另一个监控缓冲区搜索帧的代码模块。理想情况下,这个缓冲区足够大,可以将帧留在那里并为下一帧留出空间,并且不需要另一个缓冲区来保存接收到的帧的副本。使用循环缓冲区,第二个模块可以将头指针移动(必要时丢弃)到帧标记的开头并等待完整帧的数据。一旦出现完整的帧,它就会调用另一个处理该帧的函数。该功能可能是您要询问的功能。而“只是编码”可能是答案,你在一个微控制器中,所以你不能在操作系统解决方案上使用惰性高级桌面应用程序。如果您自己创建或通过库提供给您,或者不取决于您的解决方案,您将需要某种 strcmp 函数。蛮力 if(strncmp(&frame[1],"bob",3)==0) then, else if(strncmp(&frame[1],"ted",3) then, else if... 当然有效,但是你可能会用那种东西咀嚼你的rom,也可能不会。这种方法所需的缓冲可能会消耗很多内存。这种方法非常可读、可维护,而且可移植。可能不会很快(可维护通常与可靠性和/或性能相冲突),但这可能不是问题,只要您可以在下一个出现之前处理这个,或者在未处理的数据掉出循环缓冲区之前。根据任务帧检查器例程可能只是检查帧是否良好,我通常会放置开始和结束标记、长度和某种算术校验和,如果它是坏帧,它会被丢弃,这样可以节省大量代码检查坏/损坏数据。当帧处理例程返回到搜索帧例程时,它移动头指针以清除帧,因为它不再是 n eded,好框架或坏。帧检查器可能只验证一个帧并将其交给另一个进行解析的函数。这种排列中的每个乐高积木都有一个非常简单的任务,并且假设它下面的乐高积木已经正确执行了它的任务。模块化、面向对象,无论您想使用什么术语,都可以使设计、编码、维护和调试变得更加容易。 (以性能和资源为代价)。这种方法适用于任何串行类型的流,无论是微控制器中的串行端口(具有足够的资源),还是桌面上的应用程序从串行端口查看串行数据或 TCP 数据,这些数据也是串行的而不是面向帧的。

    如果您的微机没有所有这些资源,那么状态机方法也可以很好地工作。每个到达的字节都会在状态机上标记一个状态。开始空闲等待第一个字节,第一个字节是$吗?不丢弃它并回到空闲状态。如果第一个字节是 $ 则进入下一个状态。如果您正在寻找命令“and”、“add”、“or”和“xor”,那么第二个状态将与“a”、“o”和“x”进行比较,如果这些都不是,那么去闲置。如果是 a 则进入比较 n 和 d 的状态,如果是 o 则进入寻找 r 的状态。如果在寻找 r 或状态时没有看到 r,则进入空闲状态,如果看到,则处理命令,然后进入空闲状态。从某种意义上说,代码是可读的,您可以查看状态机并查看 a、n、d、a、d、d、o、r、x、o、r 以及它们最终导致的位置,但通常不被视为可读代码。这种方法使用很少的 ram,更多地依赖于 rom,但与其他解析方法相比,总体上也可以使用最少的 rom。这里又是非常便携的,超越了微控制器,但是在微控制器之外,人们可能会认为你对这种代码很疯狂(当然,如果这是 verilog 或 vhdl,那就不是了)。这种方法更难维护、更难阅读,但非常快速和可靠,并且使用的资源最少。

    无论命令被解释后采用何种方法,您都必须确保执行命令而不会丢失串行端口上的任何字节,无论是通过代码的确定性性能还是中断或其他方式。

    底线 ascii 接口总是丑陋的,它们的代码,无论您使用多少层库来简化工作,执行的结果指令都很丑陋。根据定义,一种尺寸不适合任何人。只需开始编码,尝试状态机并尝试 if-then-else-strncmp 以及其间的优化。您应该很快就会看到哪一个在您的编码风格、工具/处理器和正在解决的问题方面表现最好。

    【讨论】:

    • 谢谢!到目前为止,您和 shodanex 的答案对我来说是最有用的。有限状态机似乎是正确的选择,尽管它不像模块化方法那么直观。而且,是的,ASCII 接口很丑。虽然不是我的选择:P
    【解决方案3】:

    这取决于你想得到多少花哨,有多少不同的命令,以及是否有可能频繁添加新命令。

    您可以创建一个数据结构,将每个有效的命令字符串与相应的函数指针相关联 - 使用bsearch() 访问的排序列表可能没问题,尽管哈希表是一种可能具有更好性能的替代方案(因为有效命令是事先知道的,你可以用gperf之类的工具构造一个完美的哈希。

    bsearch() 方法可能看起来像这样:

    void func_aa(char args[11]);
    void func_cc(char args[11]);
    void func_xy(char args[11]);
    
    struct command {
        char *name;
        void (*cmd_func)(char args[11]);
    } command_tbl[] = {
        { "AA", func_aa },
        { "CC", func_cc },
        { "XY", func_xy }
    };
    
    #define N_CMDS (sizeof command_tbl / sizeof command_tbl[0])
    
    static int comp_cmd(const void *c1, const void *c2)
    {
        const struct command *cmd1 = c1, *cmd2 = c2;
    
        return memcmp(cmd1->name, cmd2->name, 2);
    }
    
    static struct command *get_cmd(char *name)
    {
        struct command target = { name, NULL };
    
        return bsearch(&target, command_tbl, N_CMDS, sizeof command_tbl[0], comp_cmd);
    }
    

    然后,如果您有 command_str 指向来自串行端口的字符串,您将执行此操作以调度正确的函数:

    struct command *cmd = get_cmd(command_str + 1);
    
    if (cmd)
        cmd->cmd_func(command_str);
    

    【讨论】:

    • 谢谢!我需要阅读哈希表和哈希函数:D
    【解决方案4】:

    不知道你是否还在做这个。但我正在做一个类似的项目,发现了一个嵌入式命令行解释器http://sourceforge.net/projects/ecli/?source=recommended。没错,他们想到了嵌入式应用程序。

    cli_engine 函数确实有助于从命令行获取输入。

    警告:除了自述文件之外没有任何文档。我仍在解决一些集成框架的错误,但这绝对让我有了一个良好的开端。您必须自己处理比较字符串(即使用 strcmp)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-25
      • 1970-01-01
      • 2010-10-28
      • 2012-09-26
      • 2010-11-14
      • 2023-03-06
      相关资源
      最近更新 更多