4

我正在为 MCU(ARM Cortex-M3)开发固件。这些设备没有很多 RAM,所以无论如何你应该尝试将数据放在恒定内存(闪存)中。

问题是这样的:设备必须提供将由接口(MODBUS)读取的“寄存器”,因此操作员读取“地址”10并获得一些数字,他/她“写入”到“地址”101和它会导致一些动作等。将有数百个这样的“地址”并且访问它们会导致一些动作 - 例如,从 1-10 读取会导致传感器 1 到 10 上的温度测量,从 11-20 读取会导致读取在一些校准值中,写入这些地址会导致这些校准值存储在非易失性存储器等上 - 会有很多不同的功能(;

目前我已经实现了这样的:

  1. 有一个数组将地址绑定到回调函数以进行读写 - 一个回调函数可以绑定到多个地址(就像在上面的示例中,相同的回调将用于 1-10)

  2. 还有另一个数组将地址绑定到回调参数,有很多这样的数组,因为参数可能具有不同的类型/大小 - 在上面的示例中,将有一个结构数组 {int address; int sensor;} 用于 1-10 和结构数组 {int address; 内部标识;整数大小;最小; 最大整数;int 默认值;} 11-20。

  3. 每个回调都可以获取当前地址,在它的数组中找到相关的struct,并获取它需要的参数

这种方法有点重复,因为我必须多次声明地址 - 主数组中有一个条目 {1, readSensor, writeSensor},数组或传感器中有另一个地址为 1 的条目 {1, 0x5423} - 它只是没有'不符合 DRY 原则 (;

我考虑过的一个解决方案是一组多态对象,但是:

一个。虚函数导致对象被放置在 RAM 中(它不是 ROMable) 编辑:这似乎是由 GCC 错误引起的,在 4.6 constexpr 构造函数导致对象被放置在 RAM 中,但对于 4.7 它可以工作!

湾。这仍然有点麻烦,因为我必须在“某处”创建对象,并将其地址放入数组中(该数组实际上会放在闪存中)

我不能使用任何像向量这样的 STL 东西,因为它完全放在 RAM 中。

我考虑过一些模板魔法,但那是黑魔法(;

我也考虑过一个链表,但我只是看不到任何“好”的方式来以可读和连续的形式(如数组 [; )声明它,但我可能不熟悉一些好的解决方案这个问题。

最简单的解决方案是让回调接受另一个“void *”参数并将其转换为内部需要的任何内容,但这“不好”,还需要我创建带有“某处”参数的结构,然后将它们绑定到主大批。

任何关于优雅解决方案的想法?这必须在 ROM 中,会有数百个条目,每个条目都可以有多个不同的参数。

4

3 回答 3

2

I would definitely opt for a switch .. case statement. It is very likely that your compiler will resolve it to a branch table, which is as efficient as the callback function vector you implemented, but much more readable, in my opinion. It goes like this:

void parse_modbus (int register, bool write, int more_parameters) {
    switch (register) {

    case TEMPERATURE_REGISTER_1:
    ...
    case TEMPERATURE_REGISTER_10:
        read_temperature(register - TEMPERATURE_REGISTER_1);
        break;

    case CALIBRATION_REGISTER_1:
    ...
    case CALIBRATION_REGISTER_10:
        if (write)
            write_calibration(register - CALIBRATION_REGISTER_1);
        else
            read_calibration((register - CALIBRATION_REGISTER_1);
        break;

    default:
        unimplemented_register(register);
        break;
    }
}

You still need look-up tables for min-max limits, though.

于 2013-01-21T08:10:53.390 回答
2

我们用 X-Macros 解决了这个问题。

这个想法是使用寄存器创建一个可读文件,该文件将通过多次包含它来创建适当的代码。

  • 寄存器.h
typdef enum {
  MR_SET_VOLTAGE = 100,  // 8bit-Register for the voltage R/W
  MR_SET_CURRENT = 104,  // 8bit-Register for the current R/W
  MR_ACT_VOLTAGE = 206,  // 16bit-Register for the actual Voltage Read-Only
} teRegister;
  • allRegister.inc
REGISTER(uint8_t,MR_SET_VOLTAGE,readFnA,writeFnA)
REGISTER(uint8_t,MR_SET_CURRENT,readFnA,writeFnA)
REGISTER(uint16_t,MR_ACT_VOLTAGE,readFnB,NULL)
  • 寄存器.c
#define PASTE(a,b)  a##b
#define REGISTER(_type,_regName,_readCallback,_writeCallback) \
    static _type PASTE(VAR_,_regName);

#include "allRegister.inc"

#undef REGISTER

typedef uint8_t (*tfnRegistercallback)(tsRegister const *pRegister);

typedef struct {
  uint8_t registerIdx;
  uint8_t registerSize;
  tfnRegistercallback readCallback;
  tfnRegistercallback writeCallback;
  void *pData;  // This is a pointer to the data of the register (in RAM)
} tsRegister;


// Creating of the array of all registers
#define REGISTER(_type,_regName,_readCallback,_writeCallback) \
    {_regName, sizeof(_type), _readCallback,_ writeCallback, &PASTE(VAR_,_regName)},

// Array of all registers, resides in Flash
static const tsRegister allRegister[] = {
#include "allRegister.inc"
};

#undef REGISTER

它为每个寄存器创建一个变量,static uint8_t VAR_MR_SET_VOLTAGE;
并为每个寄存器创建一个数组条目。

static const tsRegister allRegister[] = {
   { MR_SET_VOLTAGE, sizeof(uint8_t), readFnA, writeFnA, &VAR_MR_SET_VOLTAGE},
   ...
};

并且回调函数得到一个指向寄存器的 const 条目的指针,所以函数本身可以用于多个寄存器。

void readCallback(tsRegister const *pRegister)
{
  int value;
  if (pRegister->registerSize == 1 )
    value = *(uint8_t*)pData;
  else if (pRegister->registerSize == 2 )
    value = *(uint16_t*)pData;

  if ( pRegister->register == MR_ACT_VOLTAGE )
    doSomething();
}
于 2013-01-21T12:45:52.320 回答
2

我可能会为此编写一些代码来提供“源代码”,并且有一个“编译器”。

所以你的原始来源是这样的:

# INPUT_SENSOR(callback1, callback2, address, sensor_id)
SENSOR(read_sensor, write_sensor, 100, 1)
SENSOR(read_sensor, write_sensor, 104, 2)
 ...
# CALIBTRATE(callback1, callback2, id, address, size, min, max, default)
CALIBRATE(calib_write, calib_read, 1, 44, 11, 18, 99, 33)
CALIBRATE(calib_write, calib_read, 2, 45, 12, 19, 98, 34)

然后,您可以让它生成这样的数据结构:

struct funcptrs {
   int (*readfunc)(int count, int arr[]); 
   void (*writefunc)(int count, int arr[]); 
   int count;
   int *arr; 
};

static const int arr1[] = { 100, 1 };
static const int arr2[] = { 104, 2 };

static const int arr3[] = { 1, 44, 11, 18, 99, 33 };
static const int arr4[] = { 2, 45, 12, 19, 98, 34 };

struct funcptrs functable[] = 
{
    { read_sensor, write_sensor, 2, arr1 },
    { read_sensor, write_sensor, 2, arr2 },
    { calib_write, calib_read, 6, arr3 }, 
    { calib_write, calib_read, 6, arr4 }, 
};

也许可以使用 C 预处理器提出这个问题并运行两次,也许 - 我过去做过这种事情,但我有点懒得在这里尝试 - 我想我' d 宁愿写一段 20-30 行的 C 代码来生成代码,因为它更灵活,通常更容易理解/遵循。

于 2013-01-18T22:38:14.853 回答