代码之家  ›  专栏  ›  技术社区  ›  jpinto3912

不使用JMP或LCALL调用任意函数的最佳策略

  •  3
  • jpinto3912  · 技术社区  · 16 年前

    在嵌入式C中,有一些固定的/通用的算法是很自然的,但是有多个可能的实现。这是由于一些产品演示,有时是选项,有时只是产品路线图策略的一部分,如可选RAM、不同IP设置的MCU、提高频率等。

    当然,我使用C函数指针机制,并为这些指针使用一组有意义的名称。例如。

    unsigned char (*ucEvalTemperature)(int *);
    

    把温度存储在int中,然后返回OKness。

    unsigned char ucReadI2C_TMP75(int *);
    

    从TMP75设备读取I2C总线上温度的功能,以及

    unsigned char ucReadCh2_ADC(unsigned char *);
    

    这是相同的基本功能,但在不同的选项集产品上。

    这个问题在需要不同参数集的函数上变得很明显。签名永远不会正确,编译器也无法解析我的Fpointers。

    所以,我有三种方法:

    • 使用参数的全局堆栈,所有函数都是无符号char Func(void);
    • 对每个实现使用一个helper函数,它允许我打开要进行/调用的正确分配;

    既不优雅,也不沉稳。。。你的方法/建议是什么?

    3 回复  |  直到 16 年前
        1
  •  2
  •   kgiannakakis    16 年前

    我通常更喜欢分层架构。通过“驱动程序”实现与硬件的通信。算法层调用由驱动程序实现的函数(readTemp)。关键是需要定义一个接口,所有驱动程序实现都必须遵守这个接口。

    高层应该不知道温度是如何读取的(使用TMP75或ADC并不重要)。驱动程序体系结构的缺点是通常不能在运行时切换驱动程序。对于大多数嵌入式项目来说,这不是问题。如果要执行此操作,请定义指向驱动程序公开的函数(遵循公共接口)的函数指针,而不是指向实现函数的函数指针。

        2
  •  1
  •   jpalecek    16 年前

    struct Device {
      int (*read_temp)(int*, Device*);
    } *dev;
    

    称之为:

    dev->read_temp(&ret, dev);
    

    struct COMDevice {
      struct Device d;
      int port_nr;
    };
    

    当你用这个的时候,只是沮丧。

    然后,您将为您的设备创建功能:

    int foo_read_temp(int* ret, struct Device*)
    {
      *ret = 100;
      return 0;
    }
    
    int com_device_read_temp(int* ret, struct Device* dev)
    {
      struct COMDevice* cdev = (struct COMDevice*)dev; /* could be made as a macro */
      ... communicate with device on com port cdev->port_nr ...
      *ret = ... what you got ...
      return error;
    }
    

    Device* foo_device= { .read_temp = foo_read_temp };
    COMDevice* com_device_1= { .d = { .read_temp = com_read_temp },
      .port_nr = 0x3e8
    };
    COMDevice* com_device_1= { .d = { .read_temp = com_read_temp },
      .port_nr = 0x3f8
    };
    

    你将把设备结构传给需要读取温度的函数。

    这个(或类似的东西)在Linux内核中使用,除了它们不将函数指针放在结构中,而是为它创建一个特殊的静态结构,并在设备结构中创建指向这个结构的stor指针。这几乎就是如何像C++那样面向对象的语言实现多态性。

    如果将这些函数放在一个单独的编译单元中,包括引用它们的设备结构,那么仍然可以节省空间,并在链接时忽略它们。

    如果您需要不同类型的参数,或者更少的参数,请忘记它。这意味着您不能为想要更改的内容设计公共接口(在任何意义上),但是没有公共接口,就不可能实现可更改的功能。您可以使用编译时多态性(例如,typedefs&separate编译单元用于不同的实现,其中一个将在二进制文件中链接),但它至少必须与源代码兼容,即以相同的方式调用。

        3
  •  0
  •   MSalters    16 年前

    正确的方法是使用helper函数。当然,是 unsigned char ucReadCh2_ADC(unsigned char *); 代表 ?

    另一方面,如果你输入def unsigned long milliKelvin unsigned char (*EvalTemperature)(milliKelvin *out); 更清楚了。对于每一个函数,应该如何包装变得很清楚——通常是一个微不足道的函数。

    注意,我从typedef中删除了“uc”前缀,因为函数无论如何都不返回无符号字符。它返回一个布尔值,OK ness。

    推荐文章