Best strategy for calling an arbitrary function without using JMP or LCALL

In embedded C, it’s natural to have some fixed / general algorithm, but more than one possible implementation. This is due to several product presentations, sometimes options, while its just part of the product’s roadmap strategies, such as additional RAM, a different MC-set MCU, increased frequency, etc.

In most of my projects, I understand that separating the main material, algorithm and logical architecture from the actual functions that implement an external state assessment, saving time, memory, etc.

Naturally, I use the C function pointer mechanism, and I use a set of meaningful names for these pointers. For example.

unsigned char (*ucEvalTemperature)(int *);

The one who stores the temperature in int and returns OKness.

Now imagine that for a specific product configuration I have

unsigned char ucReadI2C_TMP75(int *);

a function that reads the temperature on the I2C bus from the TMP75 device and

unsigned char ucReadCh2_ADC(unsigned char *);

a function that reads the voltage drop of the diode read by the ADC, which is a way to estimate the temperature with very wide strokes.

This is the same basic functionality as for different products with options.

In some configurations, I will have ucEvalTemperature set to ucReadI2C_TMP75, and on the other ucReadCh2_ADC. In this mild case, in order to avoid problems, I have to change the type of the argument to int, because the pointer is always the same size, but the function signature is not, and the compiler will complain. Okay ... this is not a killer.

, . , Fpointers.

, :

  • , char Func (void);
  • , /;
  • JMP/LCALL (, , ), .

, ... /?

+3
3

. "". (readTemp), . , .

, ( , TMP75 ADC). , . . , , ( ), .

+2

, "":

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, , , . , - , ++, .

, Device, .

, . , ( - ) , , . (, typedefs , ), - , , , .

+1

- . , unsigned char ucReadCh2_ADC(unsigned char *); , [0,255]. , [0,255]? , ?

On the other hand, if you typed typedef unsigned long milliKelvin, typedef is unsigned char (*EvalTemperature)(milliKelvin *out);much clearer. And for each function, it becomes clear how it should be wrapped - quite often a trivial function.

Please note that I removed the “uc” prefix from typedef, as the function did not return an unsigned char anyway. It returns a boolean, OK. (Colbert fans may want to use a float to indicate the truth ;-))

0
source

Source: https://habr.com/ru/post/1710464/


All Articles