Авто-генерация кода работы с EERPOM

maxalex397
Offline
Зарегистрирован: 01.05.2020

Возможно, изобретаю велосипед. Но при разработке платы управления кофеваркой (https://prokofe.ru/plugins/forum/forum_viewtopic.php?246050) возникла необходимость сохранять в EEPROM сложную, древовидную структуру настроек. Например, нужно сохранять несколько рецептов, в каждом рецепте – несколько шагов, каждый шаг – несколько параметров разных типов. Чтобы не писать много похожего кода, решил сгенерировать его автоматически из описания структуры данных. В результате вот такой код на Java

        new StoreCGenerator( Store.newInstance().addClassField("CommonSettings")
                    .addBoolean("AutoDetectCup")
                    .addWord("MinCupWeight")
                    .addByte("PumpCleaningWaterSeconds") // Time to suck water after brewing for cleaning
                    .addByte("CleaningSeconds") // Time to clean brew group after brewing.
                    .addFloat("Temperature")
                    .endClassField().addArrayField("ScaleSettings", 8)
                    .addDWord("RawReading")
                    .endClassField().addArrayField("PIDParamsSettings", 5)
                    .addFloat("DivCoef")
                    .addFloat("DifCoef")
                    .addFloat("IntegralCoef")
                    .addFloat("ConstCoef")
                    .addFloat("OnTargetDif")
                    .addBoolean("AutoSetup")
                    .addFloat("MaxRandomShiftPercent")
                    .endClassField().addArrayField("Recipe", 8)
                    .addArrayField("Step", "StepSettings", 7)
                    .addByte("Type") // 0 - Nothing, the end, 1 - Power, 2 - Flow, 3 - Pressure
                    .addByte("Value")
                    .addByte("OppositeParameterMaxValue")
                    .addByte("OppositeParameterMinValue")
                    .addByte("DurationInSeconds")
                    .addByte("StopWhenOppositeParameterGrowsTo")
                    .addByte("StopWhenOppositeParameterFallsTo")
                    .addByte("StopOnWeight")
                    .addByte("CurveType") // 0 - Immediately, 1 - Linear, 2 - Fast, 3 - Slow
                    .endClassField()
                    .endClassField()).generateCode();

Генерирует в 10 раз больше строк кода на C:

#include <avr/eeprom.h>

class CommonSettings {
public:
  CommonSettings();
  boolean getAutoDetectCup();
  void setAutoDetectCup(boolean value);
  uint16_t getMinCupWeight();
  void setMinCupWeight(uint16_t value);
  uint8_t getPumpCleaningWaterSeconds();
  void setPumpCleaningWaterSeconds(uint8_t value);
  uint8_t getCleaningSeconds();
  void setCleaningSeconds(uint8_t value);
  float getTemperature();
  void setTemperature(float value);
};

CommonSettings::CommonSettings() {
};
boolean CommonSettings::getAutoDetectCup() {
  return eeprom_read_byte((const uint8_t*)0);
};
uint16_t CommonSettings::getMinCupWeight() {
  return eeprom_read_word((const uint16_t*)1);
};
uint8_t CommonSettings::getPumpCleaningWaterSeconds() {
  return eeprom_read_byte((const uint8_t*)3);
};
uint8_t CommonSettings::getCleaningSeconds() {
  return eeprom_read_byte((const uint8_t*)4);
};
float CommonSettings::getTemperature() {
  return eeprom_read_float((const float*)5);
};
void CommonSettings::setAutoDetectCup(boolean value) {
  eeprom_update_byte((uint8_t*)0, value);
};
void CommonSettings::setMinCupWeight(uint16_t value) {
  eeprom_update_word((uint16_t*)1, value);
};
void CommonSettings::setPumpCleaningWaterSeconds(uint8_t value) {
  eeprom_update_byte((uint8_t*)3, value);
};
void CommonSettings::setCleaningSeconds(uint8_t value) {
  eeprom_update_byte((uint8_t*)4, value);
};
void CommonSettings::setTemperature(float value) {
  eeprom_update_float((float*)5, value);
};

class ScaleSettings {
public:
  ScaleSettings();
  // Array of capacity 8
  int index = 0;
  void setIndex(int _index);
  int startAddress = 0;
  void setStartAddress(int _startAddress);
  uint32_t getRawReading();
  void setRawReading(uint32_t value);
};

ScaleSettings::ScaleSettings() {
};
uint32_t ScaleSettings::getRawReading() {
  return eeprom_read_dword((const uint32_t*)startAddress);
};
void ScaleSettings::setIndex(int _index) {
  if (index != _index) {
    int oldIndex = index;
    index = _index;
    setStartAddress(startAddress - oldIndex * 4);
  }
};
void ScaleSettings::setStartAddress(int _startAddress) {
  startAddress = _startAddress + index * 4;
};
void ScaleSettings::setRawReading(uint32_t value) {
  eeprom_update_dword((uint32_t*)startAddress, value);
};

class PIDParamsSettings {
public:
  PIDParamsSettings();
  // Array of capacity 5
  int index = 0;
  void setIndex(int _index);
  int startAddress = 0;
  void setStartAddress(int _startAddress);
  float getDivCoef();
  void setDivCoef(float value);
  float getDifCoef();
  void setDifCoef(float value);
  float getIntegralCoef();
  void setIntegralCoef(float value);
  float getConstCoef();
  void setConstCoef(float value);
  float getOnTargetDif();
  void setOnTargetDif(float value);
  boolean getAutoSetup();
  void setAutoSetup(boolean value);
  float getMaxRandomShiftPercent();
  void setMaxRandomShiftPercent(float value);
};

PIDParamsSettings::PIDParamsSettings() {
};
float PIDParamsSettings::getDivCoef() {
  return eeprom_read_float((const float*)startAddress);
};
float PIDParamsSettings::getDifCoef() {
  return eeprom_read_float((const float*)(startAddress + 4));
};
float PIDParamsSettings::getIntegralCoef() {
  return eeprom_read_float((const float*)(startAddress + 8));
};
float PIDParamsSettings::getConstCoef() {
  return eeprom_read_float((const float*)(startAddress + 12));
};
float PIDParamsSettings::getOnTargetDif() {
  return eeprom_read_float((const float*)(startAddress + 16));
};
boolean PIDParamsSettings::getAutoSetup() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 20));
};
float PIDParamsSettings::getMaxRandomShiftPercent() {
  return eeprom_read_float((const float*)(startAddress + 21));
};
void PIDParamsSettings::setIndex(int _index) {
  if (index != _index) {
    int oldIndex = index;
    index = _index;
    setStartAddress(startAddress - oldIndex * 25);
  }
};
void PIDParamsSettings::setStartAddress(int _startAddress) {
  startAddress = _startAddress + index * 25;
};
void PIDParamsSettings::setDivCoef(float value) {
  eeprom_update_float((float*)startAddress, value);
};
void PIDParamsSettings::setDifCoef(float value) {
  eeprom_update_float((float*)(startAddress + 4), value);
};
void PIDParamsSettings::setIntegralCoef(float value) {
  eeprom_update_float((float*)(startAddress + 8), value);
};
void PIDParamsSettings::setConstCoef(float value) {
  eeprom_update_float((float*)(startAddress + 12), value);
};
void PIDParamsSettings::setOnTargetDif(float value) {
  eeprom_update_float((float*)(startAddress + 16), value);
};
void PIDParamsSettings::setAutoSetup(boolean value) {
  eeprom_update_byte((uint8_t*)(startAddress + 20), value);
};
void PIDParamsSettings::setMaxRandomShiftPercent(float value) {
  eeprom_update_float((float*)(startAddress + 21), value);
};

class StepSettings {
public:
  StepSettings();
  // Array of capacity 7
  int index = 0;
  void setIndex(int _index);
  int startAddress = 0;
  void setStartAddress(int _startAddress);
  uint8_t getType();
  void setType(uint8_t value);
  uint8_t getValue();
  void setValue(uint8_t value);
  uint8_t getOppositeParameterMaxValue();
  void setOppositeParameterMaxValue(uint8_t value);
  uint8_t getOppositeParameterMinValue();
  void setOppositeParameterMinValue(uint8_t value);
  uint8_t getDurationInSeconds();
  void setDurationInSeconds(uint8_t value);
  uint8_t getStopWhenOppositeParameterGrowsTo();
  void setStopWhenOppositeParameterGrowsTo(uint8_t value);
  uint8_t getStopWhenOppositeParameterFallsTo();
  void setStopWhenOppositeParameterFallsTo(uint8_t value);
  uint8_t getStopOnWeight();
  void setStopOnWeight(uint8_t value);
  uint8_t getCurveType();
  void setCurveType(uint8_t value);
};

StepSettings::StepSettings() {
};
uint8_t StepSettings::getType() {
  return eeprom_read_byte((const uint8_t*)startAddress);
};
uint8_t StepSettings::getValue() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 1));
};
uint8_t StepSettings::getOppositeParameterMaxValue() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 2));
};
uint8_t StepSettings::getOppositeParameterMinValue() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 3));
};
uint8_t StepSettings::getDurationInSeconds() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 4));
};
uint8_t StepSettings::getStopWhenOppositeParameterGrowsTo() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 5));
};
uint8_t StepSettings::getStopWhenOppositeParameterFallsTo() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 6));
};
uint8_t StepSettings::getStopOnWeight() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 7));
};
uint8_t StepSettings::getCurveType() {
  return eeprom_read_byte((const uint8_t*)(startAddress + 8));
};
void StepSettings::setIndex(int _index) {
  if (index != _index) {
    int oldIndex = index;
    index = _index;
    setStartAddress(startAddress - oldIndex * 9);
  }
};
void StepSettings::setStartAddress(int _startAddress) {
  startAddress = _startAddress + index * 9;
};
void StepSettings::setType(uint8_t value) {
  eeprom_update_byte((uint8_t*)startAddress, value);
};
void StepSettings::setValue(uint8_t value) {
  eeprom_update_byte((uint8_t*)(startAddress + 1), value);
};
void StepSettings::setOppositeParameterMaxValue(uint8_t value) {
  eeprom_update_byte((uint8_t*)(startAddress + 2), value);
};
void StepSettings::setOppositeParameterMinValue(uint8_t value) {
  eeprom_update_byte((uint8_t*)(startAddress + 3), value);
};
void StepSettings::setDurationInSeconds(uint8_t value) {
  eeprom_update_byte((uint8_t*)(startAddress + 4), value);
};
void StepSettings::setStopWhenOppositeParameterGrowsTo(uint8_t value) {
  eeprom_update_byte((uint8_t*)(startAddress + 5), value);
};
void StepSettings::setStopWhenOppositeParameterFallsTo(uint8_t value) {
  eeprom_update_byte((uint8_t*)(startAddress + 6), value);
};
void StepSettings::setStopOnWeight(uint8_t value) {
  eeprom_update_byte((uint8_t*)(startAddress + 7), value);
};
void StepSettings::setCurveType(uint8_t value) {
  eeprom_update_byte((uint8_t*)(startAddress + 8), value);
};

class Recipe {
public:
  Recipe();
  // Array of capacity 8
  int index = 0;
  void setIndex(int _index);
  int startAddress = 0;
  void setStartAddress(int _startAddress);
  StepSettings step;
  StepSettings getStep();
};

Recipe::Recipe() {
};
StepSettings Recipe::getStep() {
  return step;
};
void Recipe::setIndex(int _index) {
  if (index != _index) {
    int oldIndex = index;
    index = _index;
    setStartAddress(startAddress - oldIndex * 63);
  }
};
void Recipe::setStartAddress(int _startAddress) {
  startAddress = _startAddress + index * 63;
  step.setStartAddress(startAddress);
};

class Store {
public:
  Store();
  CommonSettings commonSettings;
  CommonSettings getCommonSettings();
  ScaleSettings scaleSettings;
  ScaleSettings getScaleSettings();
  PIDParamsSettings pIDParamsSettings;
  PIDParamsSettings getPIDParamsSettings();
  Recipe recipe;
  Recipe getRecipe();
};

Store::Store() {
};
CommonSettings Store::getCommonSettings() {
  return commonSettings;
};
ScaleSettings Store::getScaleSettings() {
  return scaleSettings;
};
PIDParamsSettings Store::getPIDParamsSettings() {
  return pIDParamsSettings;
};
Recipe Store::getRecipe() {
  return recipe;
};

Кроме того, генерируется ещё похожий код на Java так как программа платы пишется на Java, а потом конвертируется в C под Arduino. Если кому интересно – пишите – могу поделиться кодом генераторов.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ужас! :-)

rkit
Offline
Зарегистрирован: 23.11.2016

Напиши везде inline static, хотя бы, а то никакой памяти не оберешься. И не C, а C++.

maxalex397
Offline
Зарегистрирован: 01.05.2020

Объясните, пожалуйста, на что тут будет расходоваться память, если каждого класса – по одному экземпляру? В смысле, дополнительно по ссылке на каждый экземпляр? А если сделать все методы и переменные статическими, то экземпляров и ссылок вообще не нужно будет? Т.е. минус 6 ссылок?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

maxalex397,

Вы с ума-то не сходите - столько нагенерить! Нахрена? Тем более, что в структурах даже память динамически нигде не запрашивается.

Вы слышали про такое объектно-ориентированное программирование? Нет? Оно и в Яве есть, и в С++.

Вся Ваша байда делается в три строки + сами структуры для хранения.

rkit
Offline
Зарегистрирован: 23.11.2016

На код будет расходоваться память. Не оперативная.

maxalex397
Offline
Зарегистрирован: 01.05.2020

ЕвгенийП,

А где в моем коде память динамически запрашивается и сколько её запрашивается? Структура такая вся в памяти должна быть, а это уже сейчас более 600 байт. А у меня под листья дерева переменные не выделяются. Кроме того, насколько я понимаю, в 3 строчки структуру целиком предлагаете записывать, даже если в ней один байтик поменяется? А Вам EEPROM не жалко?

rkit,

Понял, спасибо! Если не будет хватать - будет, на чем сэкономить.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

maxalex397 пишет:

А где в моем коде память динамически запрашивается и сколько её запрашивается?

Нигде. Вы читать умеете? Попробуйте прочитать пост #4 ещё раз, может со второго раза получится? Если нет, - то я дико извиняюсь.

ЕвгенийП пишет:
Тем более, что в структурах даже память динамически нигде не запрашивается.

maxalex397 пишет:

насколько я понимаю, в 3 строчки структуру целиком предлагаете записывать, даже если в ней один байтик поменяется?

Вы неправильно понимаете.

В три строчки (можно даже в две) - это с побайтовой проверкой и записью только тех байтов (именно байтов, а не полей структуры), которые поменялись.

Вот у Вас есть некоторая структура:

struct ConfigParameter {
	char name [8];
	int intParam;
	double doubleParam;
	// и ещё сколько угодно параметров
	// ...
};

Добавляем к ней две (прописью: две) строки

struct ConfigParameter {
	inline void store(const uint16_t address) { EEPROM.put(address, * this); }
	inline void load(const uint16_t address) { EEPROM.get(address, * this); }
	char name [8];
	int intParam;
	double doubleParam;
	// и ещё сколько угодно параметров
	// ...
};

и после этого спокойно используем конструкции типа 

ConfigParameter param;
.....
param.load(123);
...
param.store(123);

Можно поступить "красивше" - всунуть эти строки через наследование, шаблон или ещё как. Можно адрес сохранения внутрь структуры упрятать, но суть от этого не меняется. И не надо сходить с ума с сотнями строк на ровном месте.