4

MCU (ARM Cortex-M3) のファームウェアに取り組んでいます。これらのデバイスには大量の RAM が搭載されていないため、データをコンスタント メモリ (フラッシュ) に格納するようにしてください。

問題は次のようになります: デバイスは、インターフェイス (MODBUS) によって読み取られる「レジスタ」を提供する必要があるため、オペレーターは「アドレス」10 を読み取って数値を取得し、「アドレス」101 に「書き込み」、何百ものこれらの「アドレス」があり、それらへのアクセスは何らかのアクションを引き起こします-たとえば、1-10 からの読み取りはセンサー 1 から 10 の温度の測定を引き起こし、11-20 からの読み取りは読み取りを引き起こします。一部のキャリブレーション値のうち、これらのアドレスへの書き込みにより、これらのキャリブレーション値が不揮発性メモリなどに保存されます。さまざまな機能がたくさんあります (;

現在、次のように実装しています。

  1. 読み取りと書き込みのためにアドレスをコールバック関数にバインドする配列が 1 つあります。1 つのコールバック関数を複数のアドレスにバインドできます (上の例のように、同じコールバックが 1 ~ 10 に使用されます)。

  2. コールバックのパラメーターにアドレスをバインドする別の配列があります。パラメーターのタイプ/サイズが異なる可能性があるため、そのような配列が多数あります。上記の例では、構造体 {int address; の配列が 1 つあります。int sensor;} 1 ~ 10 および構造体の配列 {int address;} 整数 ID; int サイズ; 整数分; 整数最大; int デフォルト;} 11 ~ 20 の場合。

  3. 各コールバックは現在のアドレスを取得し、その配列で関連する構造体を見つけ、必要なパラメーターを取得できます

アドレスを複数回指定する必要があるため、このアプローチは少し反復的です。メイン配列にエントリ {1, readSensor, writeSensor} があり、配列またはセンサー {1, 0x5423} にアドレス 1 の別のエントリがあります。 DRYの原則には当てはまりません(;

私が考えた 1 つの解決策は、ポリモーフィック オブジェクトの配列でしたが、次のようになりました。

a. 仮想関数によりオブジェクトが RAM に配置されます (ROMable ではありません) 。編集:これは GCC のバグが原因のようです。4.6 では constexpr コンストラクターによりオブジェクトが RAM に配置されますが、4.7 では機能します!

b. オブジェクトを「どこかに」作成し、そのアドレスを配列に配置する必要があるため、これはまだ少し面倒です (配列は実際にはフラッシュに配置されます)。

完全にRAMに配置されるため、ベクターなどのSTLのものは使用できません。

テンプレート マジックについて考えたことがありますが、それはむしろブラック マジック (;

リンクされたリストについても考えましたが、それを読み取り可能で連続した形式 (配列 [; など) で宣言する「良い」方法は見当たりませんが、いくつかの良い解決策に慣れていない可能性があります。この問題。

最も簡単な解決策は、コールバックが別の「void *」パラメーターを受け入れ、それを内部で必要なものにキャストすることですが、それは「良くない」ものであり、パラメーターを使用して構造体を「どこか」で作成し、それらをメインにバインドする必要があります。配列。

エレガントなソリューションのアイデアはありますか? これは ROM にある必要があり、何百ものエントリがあり、それぞれが複数の異なるパラメータを持つことができます。

4

3 に答える 3

2

これを X-Macros で解決しました。

アイデアは、レジスタを使用して読み取り可能なファイルを作成することです。これにより、複数回含めることで適切なコードが作成されます。

  • register.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)
  • register.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

私は間違いなくswitch .. case声明を選びます。私の意見では、コンパイラがそれをブランチ テーブルに解決する可能性が非常に高く、これは実装したコールバック関数ベクトルと同じくらい効率的ですが、はるかに読みやすいと思います。こんなふうになります:

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

ただし、最小値と最大値の制限にはルックアップ テーブルが必要です。

于 2013-01-21T08:10:53.390 に答える
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プリプロセッサを使用してこれを考え出し、おそらく2回実行することは可能かもしれません-私は過去にそのようなことをしたことがありますが、ここでそれを試すのは少し面倒です. d ではなく、コードを生成するために 20 ~ 30 行の C コードを記述します。これは、柔軟性が高く、通常は理解しやすく、従うのが簡単なためです。

于 2013-01-18T22:38:14.853 に答える