// КНОПКА
//press() - кнопка была нажата. [однократно вернёт true]
//release() - кнопка была отпущена. [однократно вернёт true]
//click() - кнопка была кликнута, т.е. нажата и отпущена до таймаута удержания. [однократно вернёт true]
//held() - кнопка была удержана дольше таймаута удержания. [однократно вернёт true]
//held(clicks) - то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: held() без аргумента перехватит вызов! См. пример preClicks. [однократно вернёт true]
//hold() - кнопка была удержана дольше таймаута удержания. [возвращает true, пока удерживается]
//hold(clicks) - то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: hold() без аргумента перехватит вызов! См. пример preClicks. [возвращает true, пока удерживается]
//step() - режим "импульсного удержания": после удержания кнопки дольше таймаута данная функция [возвращает true с периодом EB_STEP]. Удобно использовать для пошагового изменения переменных: if (btn.step()) val++;.
//step(clicks) - то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: step() без аргумента перехватит вызов! См. пример StepMode и preClicks.
//releaseStep() - кнопка была отпущена после импульсного удержания. Может использоваться для изменения знака инкремента переменной. См. пример StepMode. [однократно вернёт true]
//releaseStep(clicks) - то же самое, но функция принимает количество кликов, сделанных до удержания. Примечание: releaseStep() без аргумента перехватит вызов! См. пример StepMode и preClicks. [однократно вернёт true]
//hasClicks(clicks) - было сделано указанное количество кликов с периодом менее EB_CLICK. [однократно вернёт true]
//state() - возвращает теукщее состояние кнопки (сигнал с пина, без антидребезга): true - нажата, false - не нажата.
//busy() - вернёт true, если всё ещё нужно вызывать tick для опроса таймаутов
//hasClicks() - вернёт количество кликов, сделанных с периодом менее EB_CLICK. В противном случае вернёт 0.
//uint8_t clicks - публичная переменная, хранит количество сделанных кликов с периодом менее EB_CLICK. Сбрасывается в 0 после нового клика.
// ЭНКОДЕР
//turn() - поворот на один щелчок в любую сторону. [однократно вернёт true]
//turnH() - поворот на один щелчок в любую сторону с зажатой кнопкой. [однократно вернёт true]
//fast() - был совершён быстрый поворот (с периодом менее EB_FAST мс) на один щелчок в любую сторону. [возвращает true, пока энкодер крутится быстро]
//right() - поворот на один щелчок направо. [однократно вернёт true]
//left() - поворот на один щелчок налево. [однократно вернёт true]
//rightH() - поворот на один щелчок направо с зажатой кнопкой. [однократно вернёт true]
//leftH() - поворот на один щелчок налево с зажатой кнопкой. [однократно вернёт true]
//dir() - направление последнего поворота, 1 или -1.
//int16_t counter - публичная переменная, хранит счётчик энкодера.
#include "config.h" // все настройки железа здесь
#include "debug.h"
#define FPSTR(pstr) (const __FlashStringHelper *)(pstr)
#define LENGTH(a) (sizeof(a) / sizeof(*a))
#if DISPLAY_I2C == 1
#include <LiquidCrystal_I2C.h>
#else
#include <LiquidCrystal.h>
#endif
#include <EncButton.h>
#include <AnalogKey.h>
#include <GyverPlanner.h>
#include <GyverStepper2.h>
#include "LiquidCrystalCyr.h"
#include "Menu.h"
#include "timer.h"
#include "buzzer.h"
#include "Screen.h"
#include "Winding.h"
#include "strings.h"
#ifndef STEPPER_A_STEPS
#define STEPPER_A_STEPS STEPPER_STEPS
#endif
#ifndef STEPPER_Z_STEPS
#define STEPPER_Z_STEPS STEPPER_STEPS
#endif
#ifndef STEPPER_A_MICROSTEPS
#define STEPPER_A_MICROSTEPS STEPPER_MICROSTEPS
#endif
#ifndef STEPPER_Z_MICROSTEPS
#define STEPPER_Z_MICROSTEPS STEPPER_MICROSTEPS
#endif
#ifndef STEPPER_Z_REVERSE
#define STEPPER_Z_REVERSE 0
#endif
#ifndef STEPPER_A_REVERSE
#define STEPPER_A_REVERSE 0
#endif
#ifndef TRANSFORMER_COUNT
#define TRANSFORMER_COUNT 3
#endif
#define STEPPER_Z_STEPS_COUNT (float(STEPPER_Z_STEPS) * STEPPER_Z_MICROSTEPS) // Число шагов на оборот по оси Z
#define STEPPER_A_STEPS_COUNT (float(STEPPER_A_STEPS) * STEPPER_A_MICROSTEPS) // Число шагов на оборот по оси A
#define STEPPER_SPEED_LIMIT 18000
#ifdef GS_FAST_PROFILE
#define PLANNER_SPEED_LIMIT 37000
#else
#define PLANNER_SPEED_LIMIT 14000
#endif
#define SPEED_LIMIT (int32_t(PLANNER_SPEED_LIMIT) * 60 / STEPPER_Z_STEPS_COUNT )
#define SPEED_INC 10
#define STEPPER_Z_MANUAL_SPEED 360
#define STEPPER_A_MANUAL_SPEED ((int)(360L * 1000 / THREAD_PITCH))
#define EEPROM_SETTINGS_VERSION 2
#define EEPROM_WINDINGS_VERSION 2
#define EEPROM_SETTINGS_ADDR 0x00
#define EEPROM_WINDINGS_ADDR 0x10
#define EEPROM_WINDINGS_CLASTER (sizeof(Winding) * WINDING_COUNT + 1)
#define WINDING_COUNT 3
Winding params[WINDING_COUNT];
int8_t currentWinding = 0;
Settings settings;
enum menu_states {
Autowinding1,
CurrentTrans,
PosControl,
miSettings,
Winding1,
Winding2,
Winding3,
StartAll,
WindingBack,
TurnsSet,//LaySet,
StepSet,
SpeedSet,
Direction,
Start,
Cancel,
ShaftPos,
ShaftStepMul,
LayerPos,
LayerStepMul,
PosCancel,
miSettingsStopPerLevel,
AccelSet,
miSettingsBack
}; // Нумерованный список строк экрана
const char *boolSet[] = { STRING_OFF, STRING_ON };
const char *dirSet[] = { "<<<", ">>>" };
const uint8_t stepSet[] = { 1, 10, 100 };
MenuItem *menuItems[] = {
new MenuItem(0, 0, MENU_01),
new ByteMenuItem(0, 1, MENU_02, MENU_FORMAT_02, &settings.currentTransformer, 1, TRANSFORMER_COUNT),
new MenuItem(0, 2, MENU_04),
new MenuItem(0, 3, MENU_05),
new ValMenuItem(1, 0, MENU_06, MENU_FORMAT_06),
new ValMenuItem(1, 1, MENU_07, MENU_FORMAT_06),
new ValMenuItem(1, 2, MENU_08, MENU_FORMAT_06),
new MenuItem(1, 3, MENU_15),
new MenuItem(1, 4, MENU_09),
new IntMenuItem(2, 0, MENU_10, MENU_FORMAT_10, NULL, 1, 9999), //new IntMenuItem(2, 1, MENU_13, MENU_FORMAT_13, NULL, 1, 99),
new FloatMenuItem(2, 1, MENU_11, MENU_FORMAT_11, NULL, 5, 9999, 5),
new IntMenuItem(2, 2, MENU_12, MENU_FORMAT_14, NULL, SPEED_INC, SPEED_LIMIT, SPEED_INC),
new BoolMenuItem(2, 3, MENU_14, NULL, dirSet),
new MenuItem(2, 4, MENU_15),
new MenuItem(2, 5, MENU_09),
new IntMenuItem(10, 0, MENU_17, MENU_FORMAT_17, &settings.shaftPos, -999, 999),
new SetMenuItem(10, 1, MENU_18, MENU_FORMAT_14, &settings.shaftStep, stepSet, 3),
new IntMenuItem(10, 2, MENU_19, MENU_FORMAT_17, &settings.layerPos, -999, 999),
new SetMenuItem(10, 3, MENU_18, MENU_FORMAT_14, &settings.layerStep, stepSet, 3),
new MenuItem(10, 4, MENU_09),
new BoolMenuItem(11, 0, MENU_22, &settings.stopPerTurns, boolSet),
new IntMenuItem(11, 1, MENU_23, MENU_FORMAT_10, &settings.acceleration, 0, 9999, 1),
new MenuItem(11, 2, MENU_09),
};
byte up[8] = { 0b00100, 0b01110, 0b11111, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000 }; // Создаем свой символ ⯅ для LCD
byte down[8] = { 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b01110, 0b00100 }; // Создаем свой символ ⯆ для LCD
#if DISPLAY_I2C == 0
LiquidCrystalCyr lcd(DISPLAY_RS, DISPLAY_EN, DISPLAY_D4, DISPLAY_D5, DISPLAY_D6, DISPLAY_D7); // Назначаем пины для управления LCD
#else
LiquidCrystalCyr lcd(DISPLAY_ADDRESS, DISPLAY_NCOL, DISPLAY_NROW);
#endif
MainMenu menu(menuItems, LENGTH(menuItems), lcd);
MainScreen screen(lcd);
GStepper2<STEPPER2WIRE> shaftStepper(STEPPER_Z_STEPS_COUNT, STEPPER_STEP_Z, STEPPER_DIR_Z, STEPPER_EN);
GStepper2<STEPPER2WIRE> layerStepper(STEPPER_A_STEPS_COUNT, STEPPER_STEP_A, STEPPER_DIR_A, STEPPER_EN);
GPlanner<STEPPER2WIRE, 2> planner;
EncButton<EB_TICK, ENCODER_CLK, ENCODER_DT, ENCODER_SW> encoder(ENCODER_INPUT); // EB_TICK - опрос действий кнопки/энкодера вручную в основном цикле программы. Оптимальный вариант.
EncButton<EB_TICK, BUTTON_STOP> pedal; // EB_TICK - опрос действий кнопки/энкодера вручную в основном цикле программы. Оптимальный вариант.
Buzzer buzzer(BUZZER);
enum { ButtonLEFT,
ButtonUP,
ButtonDOWN,
ButtonRIGHT,
ButtonSELECT
};
int16_t key_signals[] = { KEYBOARD_LEFT, KEYBOARD_UP, KEYBOARD_DOWN, KEYBOARD_RIGHT, KEYBOARD_SELECT };
AnalogKey<KEYBOARD_PIN, LENGTH(key_signals), key_signals> keyboard;
void setup() {
Serial.begin(9600);
LoadSettings();
layerStepper.disable();
shaftStepper.disable();
layerStepper.reverse(!STEPPER_Z_REVERSE);
shaftStepper.reverse(!STEPPER_A_REVERSE);
planner.addStepper(0, shaftStepper);
planner.addStepper(1, layerStepper);
lcd.createChar(0, up); // Записываем символ ⯅ в память LCD
lcd.createChar(1, down); // Записываем символ ⯆ в память LCD
lcd.begin(DISPLAY_NCOL, DISPLAY_NROW); // Инициализация LCD Дисплей
menu.Draw();
encoder.setEncType(ENCODER_TYPE);
pinMode(LIMIT_RIGHTS, INPUT);
pinMode(LIMIT_LEFT, INPUT);
}
void loop() {
encoder.tick(); // Опрос пинов энкодера/кнопки, расчёт таймаутов и вызов коллбэков
KeyboardRead();
if (encoder.turn()) {
menu.IncIndex(encoder.dir()); // Если позиция энкодера изменена, то меняем menu.index и выводим экран
menu.Draw();
}
if (encoder.click()) { // Опрашиваем действия с энкодера/кнопки при помощи if ()
switch (menu.index) // Если было нажатие, то выполняем действие, соответствующее текущей позиции курсора
{
case Autowinding1:
SaveSettings();
LoadWindings();
menu.index = Winding1;
UpdateMenuItemText(0);
UpdateMenuItemText(1);
UpdateMenuItemText(2);
break;
case Winding1:
case Winding2:
case Winding3:
currentWinding = menu.index - Winding1;
menu.index = TurnsSet;
((IntMenuItem *)menu[TurnsSet])->value = ¶ms[currentWinding].turns;
((IntMenuItem *)menu[StepSet])->value = ¶ms[currentWinding].step;
((IntMenuItem *)menu[SpeedSet])->value = ¶ms[currentWinding].speed; //((IntMenuItem *)menu[LaySet])->value = ¶ms[currentWinding].layers;
((BoolMenuItem *)menu[Direction])->value = ¶ms[currentWinding].dir;
break;
case WindingBack:
menu.index = Autowinding1;
break;
case PosControl:
menu.index = ShaftPos;
break;
case TurnsSet:
case StepSet:
case SpeedSet:
case AccelSet:
ValueEdit();
break;
case CurrentTrans:
case miSettingsStopPerLevel:
case Direction:
menu.IncCurrent(1, true);
break;
case StartAll:
AutoWindingAll(params, WINDING_COUNT);
menu.Draw(true);
break;
case Start:
SaveWindings();
AutoWindingAll(params + currentWinding, 1);
menu.index = Winding1 + currentWinding;
UpdateMenuItemText(currentWinding);
break;
case Cancel:
SaveWindings();
menu.index = Winding1 + currentWinding;
UpdateMenuItemText(currentWinding);
break;
case ShaftPos:
case LayerPos:
MoveTo((menu.index == LayerPos) ? layerStepper : shaftStepper, *((IntMenuItem *)menu[menu.index])->value);
break;
case ShaftStepMul:
case LayerStepMul:
menu.IncCurrent(1, true);
((IntMenuItem *)menu[menu.index - 1])->increment = *((SetMenuItem *)menu[menu.index])->value;
break;
case PosCancel:
menu.index = PosControl;
settings.shaftPos = 0;
settings.layerPos = 0;
break;
case miSettings:
menu.index = miSettingsStopPerLevel;
break;
case miSettingsBack:
SaveSettings();
menu.index = miSettings;
break;
}
menu.Draw();
}
}
void UpdateMenuItemText(byte i) {
((ValMenuItem *)menu[Winding1 + i])->value = params[i].turns;
}
void ValueEdit() {
menu.DrawQuotes(1);
do {
encoder.tick();
if (encoder.turn() || encoder.turnH())
menu.IncCurrent(encoder.dir() * (encoder.state() ? 10 : 1));
} while (!encoder.click());
menu.DrawQuotes(0);
}
void MoveTo(GStepper2<STEPPER2WIRE> &stepper, int &pos) {
menu.DrawQuotes(1);
stepper.enable();
stepper.setAcceleration(STEPPER_Z_STEPS_COUNT * settings.acceleration / 60); // Установка ускорения, в шагах в секунду за секунду. STEPPER_Z_STEPS_COUNT - Число шагов на оборот по оси Z
stepper.setMaxSpeed(constrain(STEPPER_Z_STEPS_COUNT / 2, 1, STEPPER_SPEED_LIMIT)); // Установка максимальной скорости. Скорость по умолчанию очень низкая, так что её требуется переопределить. При движении шаговый двигатель будет ускоряться до этой максимальной скорости и замедляться при подходе к концу движения.
int o = pos;
stepper.reset();
do {
stepper.tick();
encoder.tick();
if (encoder.turn()) {
menu.IncCurrent(encoder.dir());
stepper.setTargetDeg(pos - o);
}
} while (!encoder.click() || stepper.getStatus() != 0);
stepper.disable();
menu.DrawQuotes(0);
}
void KeyboardRead() {
static int8_t oldKey = -1;
int8_t key = keyboard.pressed();
if (oldKey != key) {
switch (key) {
case ButtonLEFT:
layerStepper.enable();
layerStepper.setSpeedDeg(STEPPER_A_MANUAL_SPEED);
break;
case ButtonRIGHT:
layerStepper.enable();
layerStepper.setSpeedDeg(-STEPPER_A_MANUAL_SPEED);
break;
case ButtonUP:
shaftStepper.enable();
shaftStepper.setSpeedDeg(STEPPER_Z_MANUAL_SPEED);
break;
case ButtonDOWN:
shaftStepper.enable();
shaftStepper.setSpeedDeg(-STEPPER_Z_MANUAL_SPEED);
break;
case ButtonSELECT: break;
default:
layerStepper.brake(); // Остановить мотор слоя резко
shaftStepper.brake(); // Остановить мотор шага резко
layerStepper.disable();
shaftStepper.disable();
}
oldKey = key;
}
if (layerStepper.getStatus())
layerStepper.tick();
if (shaftStepper.getStatus())
shaftStepper.tick();
}
double speedMult = 1;
ISR(TIMER1_COMPA_vect) {
if (planner.tickManual())
setPeriod(planner.getPeriod() * speedMult); // установить период - planner.getPeriod() возвращает время в мкс до следующего вызова tick/tickManual
else
stopTimer(); // Останавливаем таймер
}
uint32_t getSpeed() {
uint32_t p = planner.getPeriod() * speedMult;
return (p == 0) ? 0 : (60000000ul / (STEPPER_Z_STEPS_COUNT * p)); // STEPPER_Z_STEPS_COUNT - Число шагов на оборот по оси Z
}
void AutoWinding(const Winding &w, bool &direction) // Подпрограмма автоматической намотки
{
Winding current; // Текущий виток и слой при автонамотке
DebugWrite("Start"); // Вывод сообщения в консоль
current.turns = 0; // Текущий виток
current.layers = 0; // Текущий слой
current.speed = w.speed; // Текущая скорость
speedMult = 1; // Скорость
current.dir = w.dir; // Текущее направление
current.step = w.step; // Текущий шаг намотки
screen.Draw(); // Вывод информации на экран
pedal.tick(); // Опрос пинов энкодера/кнопки, расчёт таймаутов и вызов коллбэков
bool run = pedal.state(); // возвращает теукщее состояние кнопки: true - нажата, false - не нажата.
shaftStepper.enable(); // Разрешение управления двигателями намотки
layerStepper.enable(); // Разрешение управления двигателями шага
planner.setAcceleration(STEPPER_Z_STEPS_COUNT * settings.acceleration / 60L); // становка ускорения, в шагах в секунду за секунду. STEPPER_Z_STEPS_COUNT - Число шагов на оборот по оси Z
planner.setMaxSpeed(constrain(STEPPER_Z_STEPS_COUNT * w.speed / 60L, 1, PLANNER_SPEED_LIMIT)); // Установка максимальной скорости. Скорость по умолчанию очень низкая, так что её требуется переопределить. При движении шаговый двигатель будет ускоряться до этой максимальной скорости и замедляться при подходе к концу движения.
int32_t dShaft = STEPPER_Z_STEPS_COUNT * w.turns; // Количество оборотов на заданное число витков
int32_t dLayer = STEPPER_A_STEPS_COUNT * w.turns * w.step / int32_t(THREAD_PITCH); // * (direction ? 1 : -1) - Количество оборотов на заданное число витков в одном слое по оси A
int32_t p[] = { dShaft, dLayer }; // Массив с целевыми позициями осей, размер массива равен количеству осей
planner.reset(); // Сбрасываем все позиции в 0 (они и так в 0 при запуске)
initTimer(); // Настраиваем таймер
while (1) {
LR_Stat = digitalRead(LIMIT_RIGHTS);
LL_Stat = digitalRead(LIMIT_LEFT);
++count_sum;
if (LR_Stat == 1 && LL_Stat == 1) {
if (state_button == 1) {
if (count_sum == 5) {
state_button = 0;
count_sum = 0;
}
}
}
if (LR_Stat == 0 or LL_Stat == 0 && run) { // Проверка нажатой одной из кнопки
if (state_button == 0) { // Если кнопка не нажата выполняем код ниже
p[1] = -p[1]; // Меняем позицию на противоположную
DebugWrite("setTarget", p[0], p[1]);
planner.setTarget(p, RELATIVE); // p - позиция куда отправить мотор. Отправляем мотор на заданную позицию - ABSOLUTE (абсолютная координата) или RELATIVE (относительно предыдущей точки)
//direction = !direction; // Меняем направление движения в моторах
state_button = 1;
++current.layers;
startTimer(); // Запускаем таймер
setPeriod(planner.getPeriod() * speedMult); // установить период - planner.getPeriod() возвращает время в мкс до следующего вызова tick/tickManual
screen.UpdateLayers(current.layers);
}
}
//DebugWrite("current.turns", current.turns, "w.turns", w.turns);
if (current.turns >= w.turns) // w.turns - Заданное количество витков
break;
if (current.turns >= w.turns) {
screen.Message(STRING_2); // "PRESS CONTINUE "
WaitButton();
screen.Draw();
}
if (run && !planner.getStatus()) { // Run - eсли кнопка нажата - !planner.getStatus() - текущий статус: 0 - стоим, 1 - едем, 2 - едем к точке паузы, 3 -крутимся со скоростью
DebugWrite("READY");
DebugWrite("setTarget", p[0], p[1]);
planner.setTarget(p, RELATIVE); // p - позиция куда отправить мотор. Отправляем мотор на заданную позицию - ABSOLUTE (абсолютная координата) или RELATIVE (относительно предыдущей точки)
startTimer(); // Запускаем таймер
setPeriod(planner.getPeriod() * speedMult); // установить период - planner.getPeriod() возвращает время в мкс до следующего вызова tick/tickManual
screen.UpdateLayers(current.layers);
}
encoder.tick(); // Опрос пинов энкодера/кнопки, расчёт таймаутов и вызов коллбэков
pedal.tick(); // Опрос пинов энкодера/кнопки, расчёт таймаутов и вызов коллбэков
bool oldState = run;
if (pedal.press() || pedal.release()) // Нажата кнопка или отпущена
run = pedal.state(); // Записываем в переменную Run - теукщее состояние кнопки
else if (pedal.state() && encoder.click()) // Текущее состояние кнопки и кнопка енкодера нажата
run = !run;
if (run != oldState) {
if (run) {
if (current.turns) { // если цель не задали ещё, то не стартуем
noInterrupts();
planner.resume();
interrupts();
if (planner.getStatus()) { // planner.getStatus() - текущий статус: 0 - стоим, 1 - едем, 2 - едем к точке паузы, 3 -крутимся со скоростью
startTimer();
setPeriod(planner.getPeriod() * speedMult); // установить период - planner.getPeriod() возвращает время в мкс до следующего вызова tick/tickManual
}
}
} else {
noInterrupts();
planner.stop();
interrupts();
}
}
if (encoder.turn()) { // Если повернуть энкодер во время автонамотки,
current.speed = constrain(current.speed + encoder.dir() * SPEED_INC, SPEED_INC, SPEED_LIMIT); // то меняем значение скорости
speedMult = double(w.speed) / double(current.speed);
screen.UpdateSpeed(current.speed);
}
static uint32_t tmr;
if (millis() - tmr >= 500) {
tmr = millis();
current.turns = (abs(shaftStepper.pos)) / STEPPER_Z_STEPS_COUNT;
screen.UpdateTurns(current.turns % w.turns + 1);
screen.PlannerStatus(planner.getStatus());
}
}
layerStepper.disable(); // disable() в виртуальном режиме отключает сигнал с мотора (для 4-проводных драйверов)
shaftStepper.disable(); // disable() в виртуальном режиме отключает сигнал с мотора (для 4-проводных драйверов)
}
void AutoWindingAll(const Winding windings[], byte n) {
bool direction = windings[0].dir; // Направление намотки - по умолчанию 1 либо -1
for (byte i = 0; i < n; ++i) {
const Winding &w = windings[i]; // Получаем выбранную намотку с параметрами 1, 2 либо 3
if (!w.turns || !w.step || !w.speed) continue; // Выходим с автонамотки если не заданы значения количества витков, шага намотки и скорости
screen.Init(w); // Инициализация экрана
if (n > 1) {
screen.Draw(); // Вывод информации на экран
screen.Message(STRING_3, i + 1); // "WINDING %d START"
buzzer.Multibeep(2, 200, 200); // Подаем звуковой сигнал
WaitButton(); // Задержка кнопки (дребезг)
}
AutoWinding(w, direction); // Запуск автонамотки
}
screen.Message(STRING_1); // "AUTOWINDING DONE"
buzzer.Multibeep(3, 200, 200); // Подаем звуковой сигнал
WaitButton(); // Задержка кнопки (дребезг)
}
void WaitButton() {
do {
encoder.tick();
KeyboardRead();
} while (!encoder.click());
}
void LoadSettings() {
int p = EEPROM_SETTINGS_ADDR;
byte v = 0;
EEPROM_load(p, v);
if (v != EEPROM_SETTINGS_VERSION)
return;
Load(settings, p);
settings.currentTransformer = constrain(settings.currentTransformer, 1, TRANSFORMER_COUNT);
}
void SaveSettings() {
int p = EEPROM_SETTINGS_ADDR;
byte v = EEPROM_SETTINGS_VERSION;
EEPROM_save(p, v);
Save(settings, p);
}
void LoadWindings() {
int p = EEPROM_WINDINGS_ADDR + (settings.currentTransformer - 1) * EEPROM_WINDINGS_CLASTER;
byte v = 0;
EEPROM_load(p, v);
for (int j = 0; j < WINDING_COUNT; ++j)
if (v == EEPROM_WINDINGS_VERSION)
Load(params[j], p);
else
params[j] = Winding();
}
void SaveWindings() {
int p = EEPROM_WINDINGS_ADDR + (settings.currentTransformer - 1) * EEPROM_WINDINGS_CLASTER;
byte v = EEPROM_WINDINGS_VERSION;
EEPROM_save(p, v);
for (int j = 0; j < WINDING_COUNT; ++j)
Save(params[j], p);
}