Я работаю над прошивкой для MCU (ARM Cortex-M3). Эти устройства не имеют много оперативной памяти, поэтому вы должны стараться помещать данные в постоянную память (флэш-память).
Проблема заключается в следующем: устройство должно предоставлять «регистры», которые будут считываться интерфейсом (MODBUS), поэтому оператор читает «адрес» 10 и получает некоторое число, он / она «записывает» на «адрес» 101 и это вызывает некоторые действия и т. д. Будут сотни таких «адресов», и доступ к ним вызывает некоторые действия — например, чтение с 1 по 10 вызывает измерение температуры на датчиках с 1 по 10, чтение с 11 по 20 вызывает чтение для некоторых значений калибровки запись в эти адреса приводит к тому, что эти значения калибровки сохраняются в энергонезависимой памяти и т. д. — будет много разных функций (;
В настоящее время у меня это реализовано так:
есть один массив, который связывает адрес с функцией обратного вызова для чтения и записи — одна функция обратного вызова может быть связана с несколькими адресами (как в примере выше, тот же самый обратный вызов будет использоваться для 1-10)
есть другие массивы, которые связывают адрес с параметрами для обратного вызова, есть много таких массивов, поскольку параметры могут быть разного типа / размера — в приведенном выше примере будет один массив структур {int address; int sensor;} для 1-10 и массив структур {int address; int id; размер int; int min; int max; int default;} для 11-20.
каждый обратный вызов может получить текущий адрес, найти соответствующую структуру в своем массиве и получить необходимые параметры
Этот подход несколько повторяется, так как мне приходится указывать адреса несколько раз — в главном массиве есть запись {1, readSensor, writeSensor} и еще одна запись с адресом 1 в массиве или датчиках {1, 0x5423} — просто не вписывается в принцип DRY (;
Одним из решений, о котором я подумал, был массив полиморфных объектов, но:
а. виртуальные функции приводят к тому, что объект помещается в ОЗУ (он не доступен для чтения) РЕДАКТИРОВАТЬ: Похоже, это вызвано ошибкой GCC, в 4.6 конструктор constexpr заставляет объект помещаться в ОЗУ, но для 4.7 это работает!
б. это все еще немного громоздко, так как мне нужно создать объект «где-то» и поместить его адрес в массив (массив будет фактически помещен во флэш-память)
Я не могу использовать какие-либо STL-вещи, такие как векторы, так как они полностью помещены в RAM.
Я думал о некоторой магии шаблонов, но это скорее было бы черной магией (;
Я также думал о связанном списке, но я просто не вижу никакого «хорошего» способа объявить его в читаемой и непрерывной форме (например, array [;), но я, возможно, не знаком с каким-то хорошим решением для Эта проблема.
Самое простое решение — заставить обратные вызовы принять другой параметр «void *» и привести их ко всему, что им нужно внутри, но это «нехорошо», а также требует от меня создания структур с параметрами «где-то», а затем привязать их к основному массив.
Есть идеи для элегантного решения? Это ДОЛЖНО быть в ПЗУ, там будут сотни записей, и каждая из них может иметь несколько разных параметров.
Я, вероятно, написал бы некоторый код, чтобы придумать «исходный код» для этого, и вроде бы иметь «компилятор».
Таким образом, ваш первоначальный источник для этого будет что-то вроде этого:
# 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, и пробежаться через него дважды, возможно — я делал это в прошлом, но я немного ленив, чтобы попробовать это здесь — я думаю, что ‘ я предпочел бы написать фрагмент кода на 20-30 строк для создания кода, так как он более гибкий и обычно более легкий для понимания / отслеживания.
Я бы определенно выбрал 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;
}
}
Тем не менее, вам все еще нужны справочные таблицы для минимальных и максимальных ограничений.
Мы решили это с помощью X-Macros.
Идея состоит в том, чтобы создать удобочитаемый файл с регистрами, который создаст соответствующий код, включив его несколько раз.
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;
REGISTER(uint8_t,MR_SET_VOLTAGE,readFnA,writeFnA)
REGISTER(uint8_t,MR_SET_CURRENT,readFnA,writeFnA)
REGISTER(uint16_t,MR_ACT_VOLTAGE,readFnB,NULL)
#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},
...
};
А функции обратного вызова получают указатель на постоянную запись в регистре, поэтому сама функция может использоваться для нескольких регистров.
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();
}