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

发布于 2024-12-03 14:24:43 字数 761 浏览 2 评论 0原文

您好,

我正在寻找重新分级设计模式的信息,以处理 C99 中的大量函数。

背景:
我正在为我的宠物项目(台式 CNC 铣床)开发完整的 G 代码解释器。目前,命令通过串行接口发送到 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)假设这些函数具有相同的签名(这就是我所倾向的),有没有办法typedef该签名的泛型类型来传递和调用?

对于分散的提问,我深表歉意。预先感谢您的帮助。

Greetings and salutations,

I am looking for information regrading design patterns for working with a large number of functions in C99.

Background:
I am working on a complete G-Code interpreter for my pet project, a desktop CNC mill. Currently, commands are sent over a serial interface to an AVR microcontroller. These commands are then parsed and executed to make the milling head move. a typical example of a line might look like

N01 F5.0 G90 M48 G1 X1 Y2 Z3

where G90, M48, and G1 are "action" codes and F5.0, X1, Y2, Z3 are parameters (N01 is the optional line number and is ignored). Currently the parsing is coming along swimmingly, but now it is time to make the machine actually move.

For each of the G and M codes, a specific action needs to be taken. This ranges from controlled motion to coolant activation/deactivation, to performing canned cycles. To this end, my current design features a function that uses a switch to select the proper function and return a pointer to that function which can then be used to call the individual code's function at the proper time.

Questions:
1) Is there a better way to resolve an arbitrary code to its respective function than a switch statement? Note that this is being implemented on a microcontroller and memory is EXTREMELY tight (2K total). I have considered a lookup table but, unfortunately, the code distribution is sparse leading to a lot of wasted space. There are ~100 distinct codes and sub-codes.

2) How does one go about function pointers in C when the names (and possibly signatures) may change? If the function signatures are different, is this even possible?

3) Assuming the functions have the same signature (which is where I am leaning), is there a way to typedef a generic type of that signature to be passed around and called from?

My apologies for the scattered questioning. Thank you in advance for your assistance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

夏花。依旧 2024-12-10 14:24:43

1) 完美散列可用于将关键字映射到标记数字(操作码),其可用于索引函数指针表。所需参数的数量也可以放入此表中。

2)你不需要重载/异构函数。可选参数可能是可能的。

3)你唯一的选择是使用可变参数,恕我直言

1) Perfect hashing may be used to map the keywords to token numbers (opcodes) , which can be used to index a table of function pointers. The number of required arguments can also be put in this table.

2) You don's want overloaded / heterogeneous functions. Optional arguments might be possible.

3) your only choice is to use varargs, IMHO

白色秋天 2024-12-10 14:24:43

我不是嵌入式系统方面的专家,但我有 VLSI 方面的经验。如果我说的是显而易见的事情,我很抱歉。

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

  1. 将所有操作代码安排在地址中连续。
  2. 实现类似于普通处理器中的操作码解码器的动作代码解码器。

第一个选项可能是更好的方法(简单且内存占用小)。但如果您无法控制操作代码,则需要通过另一个查找表实现解码器。

我不完全确定你所说的“函数签名”是什么意思。函数指针应该只是一个由编译器解析的数字。

编辑:
不管怎样,我认为两个查找表(一个用于函数指针,一个用于解码器)仍然比一个大的 switch 语句小得多。对于不同的参数,使用“虚拟”参数使它们全部一致。我不确定将所有内容强制转换为指向结构的空指针会在嵌入式处理器上产生什么后果。

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

I'm not an expert on embedded systems, but I have experience with VLSI. So sorry if I'm stating the obvious.

The function-pointer approach is probably the best way. But you'll need to either:

  1. Arrange all your action codes to be consecutive in address.
  2. Implement an action code decoder similar to an opcode decoder in a normal processor.

The first option is probably the better way (simple and small memory footprint). But if you can't control your action codes, you'll need to implement a decoder via another lookup table.

I'm not entirely sure on what you mean by "function signature". Function pointers should just be a number - which the compiler resolves.

EDIT:
Either way, I think two lookup tables (1 for function pointers, and one for decoder) is still going to be much smaller than a large switch statement. For varying parameters, use "dummy" parameters to make them all consistent. I'm not sure what the consequences of force casting everything to void-pointers to structs will be on an embedded processor.

EDIT 2:
Actually, a decoder can't be implementated with just a lookup table if the opcode space is too large. My mistake there. So 1 is really the only viable option.

梦冥 2024-12-10 14:24:43

有没有比 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 中创建一个使用可变参数(在不同时间)指向具有或多或少参数(不同签名)的函数的函数指针。

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

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

有没有办法 typedef 该签名的泛型类型
被传递和调用?

是的。
我见过的几乎所有代码都使用了函数指针
还创建一个 typedef 来引用该特定类型的函数。
(当然,混淆的竞赛条目除外)。

有关详细信息,请参阅上面的示例和维基教科书:C 编程:函数指针

附:
您重新发明轮子有什么原因吗?
也许以下 AVR 预先存在的 G 代码解释器之一可以为您工作,也许需要进行一些调整?
5D,
短跑运动员
马林
茶杯固件
sjfw,
Makerbot
或者
Grbl
(请参阅http://reprap.org/wiki/Comparison_of_RepRap_Firmwares)。

Is there a better way ... than a switch statement?

Make a list of all valid action codes (a constant in program memory, so it doesn't use any of your scarce RAM), and sequentially compare each one with the received code. Perhaps reserve index "0" to mean "unknown action code".

For example:

// 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;
}

How does one go about function pointers in C when the names (and
possibly signatures) may change? If the function signatures are
different, is this even possible?

It's possible to create a function pointer in C that (at different times) points to functions with more or less parameters (different signatures) using varargs.

Alternatively, you can force all the functions that might possibly be pointed to by that function pointer to all have exactly the same parameters and return value (the same signature) by adding "dummy" parameters to the functions that require fewer parameters than the others.

In my experience, the "dummy parameters" approach seems to be easier to understand and use less memory than the varargs approach.

Is there a way to typedef a generic type of that signature
to be passed around and called from?

Yes.
Pretty much all the code I've ever seen that uses function pointers
also creates a typedef to refer to that particular type of function.
(Except, of course, for Obfuscated contest entries).

See the above example and Wikibooks: C programming: pointers to functions for details.

p.s.:
Is there some reason you are re-inventing the wheel?
Could maybe perhaps one of the following pre-existing G-code interpreters for the AVR work for you, perhaps with a little tweaking?
FiveD,
Sprinter,
Marlin,
Teacup Firmware,
sjfw,
Makerbot,
or
Grbl?
(See http://reprap.org/wiki/Comparison_of_RepRap_Firmwares ).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文