C语言中围绕大量离散函数的设计

时间:2011-09-07 22:06:27

标签: c design-patterns function-pointers microcontroller

问候和致意,

我正在寻找重新设计模式的信息,以便在C99中使用大量功能。

背景
我正在为我的宠物项目 - 台式数控铣床开发一个完整的G-Code解释器。目前,命令通过串行接口发送到AVR微控制器。然后解析并执行这些命令以使铣头移动。一行的典型示例可能看起来像

N01 F5.0 G90 M48 G1 X1 Y2 Z3

其中G90,M48和G1是“动作”代码,F5.0,X1,Y2,Z3是参数(N01是可选的行号,被忽略)。目前解析正在顺利进行,但现在是时候让机器真正移动了。

对于G和M代码中的每一个,需要采取特定的措施。其范围从受控运动到冷却剂激活/停用,再到执行固定循环。为此,我目前的设计功能是一个函数,它使用一个开关来选择正确的函数并返回一个指向该函数的指针,然后可以用它在适当的时候调用单个代码的函数。

问题:
1)有没有比switch语句更好的方法将任意代码解析为各自的函数?请注意,这是在微控制器上实现的,内存非常严格(总共2K)。我考虑过一个查找表,但不幸的是,代码分布稀疏导致了大量的浪费空间。有大约100个不同的代码和子代码。

2)当名称(和可能的签名)可能改变时,如何在C中使用函数指针?如果功能签名不同,这甚至可能吗?

3)假设函数具有相同的签名(这是我倾向于的地方),有没有办法键入一个泛型类型的签名来传递和调用?

我为分散的提问道歉。提前感谢您的帮助。

3 个答案:

答案 0 :(得分:1)

我不是嵌入式系统的专家,但我有VLSI的经验。很抱歉,如果我说的那么明显。

函数指针方法可能是最好的方法。但你需要:

  1. 将所有行动代码安排在地址中连续。
  2. 在普通处理器中实现类似于操作码解码器的动作码解码器。
  3. 第一种选择可能是更好的方法(简单和小内存占用)。但如果您无法控制您的操作代码,则需要通过另一个查找表来实现解码器。

    我不完全确定“函数签名”的含义。函数指针应该只是一个数字 - 编译器解析它。

    编辑: 无论哪种方式,我认为两个查找表(1个用于函数指针,一个用于解码器)仍然比一个大型switch语句小得多。对于不同的参数,使用“虚拟”参数使它们一致。我不确定强制将所有内容强制转换为结构的void指针的后果将在嵌入式处理器上。

    编辑2: 实际上,如果操作码空间太大,则不能仅使用查找表来实现解码器。我的错误在那里。所以1真的是唯一可行的选择。

答案 1 :(得分:1)

1)可以使用完美散列将关键字映射到令牌编号(操作码),这些编号可用于索引函数指针表。所需参数的数量也可以放在此表中。

2)您不需要过载/异构功能。可能的参数是可能的。

3)你唯一的选择是使用varargs,恕我直言

答案 2 :(得分:1)

  

有没有比switch语句更好的方法?

列出所有有效的操作代码(程序存储器中的常量,因此它不使用任何稀缺的RAM),并按顺序将每个代码与接收到的代码进行比较。也许保留索引“0”表示“未知动作代码”。

例如:

// Warning: untested code.
typedef int (*ActionFunctionPointer)( int, int, char * );
struct parse_item{
    const char action_letter;
    const int action_number; // you might be able to get away with a single byte here, if none of your actions are above 255.
    // alas, http://reprap.org/wiki/G-code mentions a "M501" code.
    const ActionFunctionPointer action_function_pointer;
};
int m0_handler( int speed, int extrude_rate, char * message ){ // M0: Stop
    speed_x = 0; speed_y = 0; speed_z = 0; speed_e = 0;
}
int g4_handler ( int dwell_time, int extrude_rate, char * message ){ // G4: Dwell
    delay(dwell_time);
}
const struct parse_item parse_table[] = {
    { '\0', 0, unrecognized_action }  // special error-handler 
    { 'M', 0, m0_handler }, // M0: Stop
    // ...
    { 'G', 4, g4_handler }, // G4: Dwell
    { '\0', 0, unrecognized_action }  // special error-handler 
}
ActionFunctionPointer get_action_function_pointer( char * buffer ){
    char letter = get_letter( buffer );
    int action_number = get_number( buffer );
    int index = 0;
    ActionFunctionPointer f = 0;
    do{
        index++;
        if( (letter == parse_table[index].action_letter ) and
            (action_number == parse_table[index].action_number) ){
            f = parse_table[index].action_function_pointer;
        };
        if('\0' == parse_table[index].action_letter ){
            index = 0;
            f = unrecognized_action;
        };
    }while(0 == f);
    return f;
}
  

如何在名称(和。)中使用C语言中的函数指针   可能签名)可能会改变?如果功能签名是   不同,这甚至可能吗?

可以在C中创建一个函数指针,该函数指针(在不同时间)使用varargs指向具有更多或更少参数(不同签名)的函数。

或者,您可以通过向需要较少参数的函数添加“dummy”参数来强制该函数指针可能指向的所有函数具有完全相同的参数和返回值(相同的签名)。其他人。

根据我的经验,“虚拟参数”方法似乎比varargs方法更容易理解和使用更少的内存。

  

有没有办法输入定义该签名的泛型类型   要传递并从中呼叫?

是。 几乎所有我见过的使用函数指针的代码 还会创建一个typedef来引用该特定类型的函数。 (当然,除了Obfuscated contest entries)。

有关详细信息,请参阅上面的示例和Wikibooks: C programming: pointers to functions

P.S .: 你有什么理由重新发明轮子吗? 或许可能是以下预先存在的AV代码G代码解释器之一为您工作,也许稍微调整一下? FiveDSprinterMarlinTeacup FirmwaresjfwMakerbot, 要么 Grbl? (见http://reprap.org/wiki/Comparison_of_RepRap_Firmwares)。