Не хочу подрывать ваш энтузиазм - но вот на моей памяти - еще с тех лет когда я школьником читал свой любимый журнал "Радио" - множество людей пытались добиться "ясности" в этом неблагодарном деле "восстановления автомобильных АКБ".. Всех благ - и успеха !
Нонсенс, но в большинстве случаев дорога к цели или постижение "ясности", гораздо более интересны, чем обладание окончательным результатом. ;)
Такова наверно, суть творческого потенциала человека. Если он имеется конечно и есть возможность его реализации.
А в остальном, ситуация по алгоритмам пока в стадии - "чем дальше в лес, тем толще партизаны" :D
Доброго дня! К сожалению в своих поделках до текущего момента обходился без энкодеров. Наверно потому, что у меня кнопок этих, ну просто завались :D И к сожалению пока не планирую отходить от этой традиции. Тем более, что это потребует существенной переделки кода, на что просто нет времени. Практика же показывает, что даже 5 кнопок можно очень компактно разместить.. Тем более одну;)
На данный момент ЗУ остается на той же аппаратной основе, программно универсальность пока убрана, поскольку возникла необходимость работы конкретно с автомобильными АКБ 12V не первой свежести.
Так что возможно пора открывать новую тему, адаптивного зарядного устройства для автомобильных аккумуляторов. Со свойственной им спецификой заряда.
Добрый день, уважаемый Домосед. Вопрос-скетч возможно ли использовать для гелевых 12В 5-7А/ч? или нужны изменения? Если да, то какие?
Добрых дней! Кто бы дал взаймы свободного времени ;( .. D)
К сожалению предыдущая схема нуждается в модификации, в целях безопасности. Поскольку импульсы тока на выходе могут привести к неприятным последствиям при неаккуратном обращении с ЗУ. Бабах в общем. И все сульфаты вместе с кислотой оказываются на стенах и потолке.
Вариант модернизации - 1
Вариант - 2 ( более действенный, но небезопасный)
Поскольку данная схемная реализация не оправдала надежд на эффективное восстановление б/у АКБ, буду её менять.
Скетч к варианту 1 прилагаю, безусловно он сырой и нуждается в доработке. Через пару дней его поправлю для удобоваримости неискушенных пользователей.
/**********************************************************************************
* !!! для аппаратной версии с конденсаторами
* --------------------------------------------------------
* !!! Версия для свинцовых 12V аккумуляторов,
* режимы: автозаряд, ручной базовый без выкрутасов, разряд,
* блок питания с регулировкой тока и напряжение, импульсный
*---------------------------------------
* Gyver core atmega328 optiboot 8
* !!! Если не GyverCore, закоментировать analogPrescaler( 64);
* вывод в serial 115200 бод
*---------------------------------------
* !!! В скетче все наряжения и токи в сотых долях, т.е. U 1240 = 12.40V, I 150 = 1.5A
* !!!
* В доработку:
* графика в меню
* анализ состояния
* расширить функциональность, добавка режимов
* сохранение параметров в EEPROM ???
*********************************************************************************/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// https://arduino.ru/forum/programmirovanie/shim-proizvolnogo-razresheniya-na-unonano-328
#include "FlexPWM.h"
/* http://arduino.ru/forum/programmirovanie/etyudy-dlya-nachinayushchikh-potokovyi-vyvod-v-serial#comment-194614 */
template <typename T> inline HardwareSerial & operator << (HardwareSerial &s, T n) { s.print(n); return s; }
/********************* Пины ********************************/
#define pinLedRed 13
#define pinLedGreen 12
#define pinLedBlue 11
#define pinLeftButton 2
#define pinRightButton 3
#define pinOkButton 4
#define pinDownButton 5
#define pinUpButton 6
#define pinADC_Voltage A1
#define pinADC_ChargeCurrent A0
#define pinADC_DischargeCurrent A2
#define pinPwmCharge 10
#define pinPwmDischarge 9
/****************** кнопки коды **************************/
#define BUT_OK 4
#define BUT_RIGHT 2
#define BUT_LEFT 1
#define BUT_UP 16
#define BUT_DOWN 8
/****************** ток **************************/
#define I_MAX 1200 // ограничение по току xx ампер
/***************** тип аккумулятора **********************/
#define BAT_PBSB 0 // сурьма - классика
#define BAT_PBCA 1 // гибрид
#define BAT_CACA 2 // кальций кальций
uint8_t batteryType = BAT_CACA;
/***************** режимы работы ********************/
#define MODE_STOP 0 // останов
#define MODE_AUTO_CHARGE 1 // автоматический заряд
#define MODE_BASE_CHARGE 2 // ручной заряд
#define MODE_DISHARGE 3 // ручной разряд
#define MODE_POWER 4 // источник питания
#define MODE_TRAINING 5 // тренировка - разряд заряд
#define MODE_1CELL 6 // работа с одной ячейкой
#define MODE_NORMAL 7
uint8_t operatingMode = MODE_STOP; // текущий режим
/***************** стадия работы **********************/
#define STOP 0 // стоп
#define ACTIVATION 1 // активация аккумулятора
#define MEASURE_PWM 2 // замер PWM для токов
#define CHARGE_ASSYMETRIC 3 // ассиметричный заряд
#define CHARGE_BASE 4 // основной заряд
#define CHARGE_RECOVERY 5 // заряд восстановления
#define CHARGE_BLEND 6 // перемешивание электролита с добивкой
#define SAVE_CHARGE 7 // хранение
#define DISCHARGE_BASE 8 // разряд
#define DISCHARGE_PULSE 9 // пульсирующий разряд для тестирования
#define DISCHARGE_TEST 10 // разряд для тестирования
#define CHARGE_TEST 11 // заряд для тестирования
#define CHARGE_PULSE1 12 // пульсирующий заряд 1 для тестирования
#define CHARGE_PULSE2 13 // пульсирующий заряд 2 для тестирования
#define ANALYSIS_CONDITION 14 // анализ состояния АКБ
#define ANALYSIS_CHARGE 15 // анализ токов заряда
#define ANALYSIS_DISCHARGE 16 // анализ токов разряда
#define POWER_SUPPLY 17 // источник питания
uint8_t workStage = STOP; // текущая стадия
uint8_t prevWorkStage = STOP; // предыдущая стадия
uint8_t nextWorkStage = STOP; // следующая стадия
/***************** фреймы дисплея **********************/
#define FRAME_INFO 0 // информационный
#define FRAME_VOLTAGE 1 // окно установки напряжений
#define FRAME_CURRENT 2 // окно установки токов
#define FRAME_DURATION 3 // окно установки длительностей
#define FRAME_MODE 11 // окно выбора режима работы
#define FRAME_START_STOP 12 // окно старт/стоп
int8_t curFrame = FRAME_INFO; // текущее окно
uint8_t prevCurFrame;
uint8_t markedLineParam = 1; // активный пункт в окне настроек параметров
uint8_t markedLineMode = 1; // активный пункт в окне выбора режимов работы
/*** Настроечные значения !!! Сотые доли !!! Пример 1220 = 12,20 ***/
int16_t voltActivationMin = 600; // напряжение начала активации 6v
int16_t voltActivationMax = 1220; // напряжение окончания активации
int16_t voltBaseMin = 1400; // напряжение уменьшения тока основного заряда
int16_t voltBaseMax = 1440; // максимальное напряжение для основного заряда
int16_t voltBlendMax = 1560; // максимальное напряжение перемешивания
int16_t voltSaveMin = 1310; // напряжение хранения минимальное
int16_t voltSaveMax = 1360; // напряжение хранения максимальное !!! определить на основном заряде - быстпый рост напряжения
int16_t voltDischargeMin = 1200; // минимальное напряжение для разряда
int16_t ampChargeActivation = 50; // ток активационного заряда
int16_t ampChargeBase = 200; // ток основного заряда
int16_t ampChargeBaseCut = 20; // ток отсечки заряда CV
int16_t ampChargeBlend = 150; // ток заряда перемешивания
int16_t ampChargeSave = 60; // ток заряда при хранении
int16_t ampDischargeMan = 150; // ток разряда
// !!! длительности в милисекундах !!!
uint16_t durActivationCharge = 2000; // длительность заряда активации
uint16_t durActivationPause = 3000; // длительность паузы активации
uint16_t durAssCharge = 3000; // длительность ассиметричного заряда
uint16_t durAssDischarge = 100; // длительность ассиметричного разряда
uint16_t durAssPause = 2000; // длительность ассиметричной паузы
uint8_t nCycles = 1; // число циклов разряд/заряд
uint16_t nImpBlend = 200; // число импульсов перемешивания
/****************** Текущие значения *******************/
uint16_t chargePWM[12]; // значения PWM для токов заряда
uint16_t dischargePWM[12]; // значения PWM для токов разряда
uint8_t capacity; // текущая емкость АКБ, в сотых долях
uint8_t intResistance; // текущее внутреннее сопротивление АКБ
uint8_t cycleCounter; // счетчик циклов зарядов/разрядов
uint16_t voltBattery; // текущее напряжение батареи
int16_t ampCharge; // текущий ток заряда
int16_t ampDischarge; // текущий ток разряда
float chargeEnergy; // полученная энергия при заряде Ам.час
float dischargeEnergy; // отданная энергия при разряде Ам.час
uint16_t pwmCharge = 0; // ШИМ заряда
uint16_t pwmDischarge = 0; // ШИМ разряда
uint32_t onTimeMill; // время от включения прибора, милисекунды
uint32_t onTimeSec = 0; // время от включения прибора, секунды
uint32_t TimeWork = 0; // время работы, секунды
uint16_t timeHours; // часы формат hh:mm:ss
uint16_t timeMins; // минуты формат hh:mm:ss
uint16_t timeSecs; // секунды формат hh:mm:ss
uint8_t nPhase; // текущая фаза в стадии
uint8_t counterPWM; // счетчик замеров PWM токов
uint16_t pulseCounter; // счетчик импульсов в пачке
uint8_t cellDivider=1; // делитель для числа ячеек, для одной банки АКБ = 6
uint32_t dt; // длительность loop для отладки
/****************** Флаги *******************/
bool Correction = false; // идет коррекция тока или напряжения
bool fAllowDisplay = true; // разрешить дисплей
bool fEdit = false; // идет редактирования параметров
bool fAnalysis = false; // идет анализ
bool fAnalysisCompleted = false; // фаза анализа завершена
bool fPause = false; // идет ПАУЗА
bool fPauseCompleted = false; // фаза паузы завершена
bool fCharge = false; // идет заряд
bool fChargeCompleted = true; // фаза заряда завершена
bool fDischarge = false; // идет разряд
bool fDischargeCompleted = true; // фаза разряда завершена
/***************************************************************************/
Adafruit_SSD1306 display( 128, 64, &Wire, -1);
void setup() {
pinMode( pinLeftButton, INPUT);
pinMode( pinRightButton, INPUT);
pinMode( pinOkButton, INPUT);
pinMode( pinDownButton, INPUT);
pinMode( pinUpButton, INPUT);
pinMode( pinLedRed, OUTPUT);
pinMode( pinLedGreen, OUTPUT);
pinMode( pinLedBlue, OUTPUT);
pinMode( pinPwmDischarge, OUTPUT);
pinMode( pinPwmCharge, OUTPUT);
display.begin( SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize( 2);
display.setTextColor( WHITE);
analogPrescaler( 64); // прескалер ADC 16, 32, 64, 128
analogReference(INTERNAL);
Serial.begin(115200);
pwmInit( 10);
workStage = STOP; // workStage = ACTIVATION; для автозапуска
nPhase = 0;
//delay( 3000);
}
void loop()
{
uint8_t charSerial;
/******** для внешнего управления по UART ****/
charSerial = Serial.read();
{
if ( charSerial == 's') // STOP
workStage = STOP;
else if ( charSerial == 'a') // ACTIVATION
workStage = ACTIVATION;
else if ( charSerial == 'c') // CHARGE_BASE
workStage = CHARGE_BASE;
else if ( charSerial == 'd') // DISCHARGE_BASE
workStage = DISCHARGE_BASE;
else if ( charSerial == 's') // SAVE_CHARGE
workStage = SAVE_CHARGE;
else if ( charSerial == 'w') // POWER_SUPPLY
workStage = POWER_SUPPLY;
else if ( charSerial == 't') // CHARGE_TEST
workStage = CHARGE_TEST;
else if ( charSerial == 'z') // DISCHARGE_TEST
workStage = DISCHARGE_TEST;
else if ( charSerial == '.'){ // CHARGE_PULSE1
workStage = MEASURE_PWM;
nextWorkStage = CHARGE_PULSE1;
}
else if ( charSerial == ':'){ // CHARGE_PULSE2
workStage = MEASURE_PWM;
nextWorkStage = CHARGE_PULSE2;
}
else if ( charSerial == 'u') // DISCHARGE_PULSE
workStage = DISCHARGE_PULSE;
else if ( charSerial == '!') // установка PWM значений
workStage = MEASURE_PWM;
else if ( charSerial == '@'){ // сброс PWM значений
resetChargePWM();
resetDischargePWM();
}
else if ( charSerial == 'p') { // увеличить ток заряда\разряда
if ( ampChargeBase < 1000) {
ampChargeBase += 10;
ampDischargeMan = ampChargeBase;
}
}
else if ( charSerial == 'm') { // уменьшить ток заряда\разряда
if ( ampChargeBase > 10) {
ampChargeBase -= 10;
ampDischargeMan = ampChargeBase;
}
}
else if ( charSerial == '+') { // увеличить напряжение
if ( voltBaseMax < 1800)
voltBaseMax += 10;
}
else if ( charSerial == '-') { // уменьшить напряжение
if ( voltBaseMax > 10)
voltBaseMax -= 10;
}
}
/*********************************************************************/
getTimeWork(); // заполнение временных отметок
procPresButtons(); // обработка по нажатию кнопок
getVoltageCurrent(); // получить напряжение батареи, токи заряда, разряда
if ( voltBattery < 300 && voltBattery > 60) // если напряжение батареи меньше 3v
operatingMode = MODE_1CELL; // ВОЗМОЖНО!!! подключена одна банка
if ( operatingMode == MODE_1CELL) // режим работы с одной банкой
cellDivider = 6; // установить делитель напряжений
else
cellDivider=1;
/******************************* предохранители ************************/
if ( pwmCharge > 0 && pwmDischarge > 0)
workStage = STOP; // останов по сквозному току
if ( ampCharge > I_MAX || ampDischarge > I_MAX)
workStage = STOP; // останов по превышению токов
if ( workStage != POWER_SUPPLY) // останов если не БП и занижено напряжение АКБ
if ( voltBattery < voltActivationMin/cellDivider)
workStage = STOP;
/************************************************************************/
ledIndication(); // индикация фазы работы RGB светодиодом
calcEnergy(); // подсчет полученной, отданной энергии
if ( fAllowDisplay) // если разрешено
displayFrame( curFrame, markedLineParam); // вывод на дисплей
/***************** отработка стадий и фаз *****************/
switch ( workStage)
{ /************* останов всех стадий работы *************/
case STOP:
ChargingProc( 0, 0, 0, 0, false); // останов заряда
DischargingProc( 0, 0, 0, false); // останов разряда
fPause = false; // сброс флагов
fCharge = false; //
fChargeCompleted = true; //
fDischarge = false; //
fDischargeCompleted = true; //
fAllowDisplay = true; // разрешить дисплей
operatingMode = MODE_STOP; // режим стоп
nPhase = 0; // номер фазы в стадии процесса
nCycles = 0; // счетчик циклов разряд заряд
cellDivider=1; // делитель для числа ячеек
pulseCounter = 0; // счетчик импульсов в пачке
break;
/************* стадия - предзаряд, активация *************/
case ACTIVATION:
switch ( nPhase) // по фазам
{
case 0:
if ( voltBattery > voltActivationMax/cellDivider) { // предзаряд актуален?
workStage = CHARGE_BASE; // нет -> заряд
nPhase = 0;
break;
}
if ( ChargingProc( ampChargeActivation, 0, 9999, durActivationCharge, false)) // заряд
nPhase++; // следующая фаза
break;
case 1:
//if ( DischargingProc( 50, 0, 1000, false)) // разряд ???
nPhase++;
break;
case 2: // пауза
if ( makePause( durActivationPause, 0))
nPhase = 0; // сброс номера фазы
break;
}
break;
/************* стадия - ассиметричный заряд или пульсирующий *************/
case CHARGE_ASSYMETRIC:
static uint16_t amp_ch;
switch ( nPhase)
{
case 0: // заряд
if ( voltBattery < voltSaveMin/cellDivider)
amp_ch = ampChargeBase;
else if ( voltBattery < voltSaveMax/cellDivider) // снижение тока
amp_ch = ampChargeBase/2;
else if ( voltBattery < voltBaseMin/cellDivider)
amp_ch = ampChargeBase/4;
if ( ChargingProc( amp_ch, 0, voltBaseMax/cellDivider, durAssCharge, true))
nPhase++;
break;
case 1: // разряд
if ( durAssDischarge) // если ненулевая длительность
if ( !DischargingProc( amp_ch, 0, durAssDischarge, true))
break; // разряд не закончен
nPhase++;
break;
case 2: // пауза
if ( makePause( durAssPause, 0)) {
if ( voltBattery > voltBaseMin/cellDivider)
workStage = CHARGE_BASE; // следующая стадия
}
nPhase = 0;
break;
}
break;
/************* базовый заряд батареи ССCV *************/
case CHARGE_BASE:
if ( operatingMode == MODE_BASE_CHARGE) {
if ( voltBattery < voltSaveMin/cellDivider)
amp_ch = ampChargeBase;
else if ( voltBattery < voltSaveMax/cellDivider) // снижение тока
amp_ch = ampChargeBase/2;
else if ( voltBattery < voltBaseMin/cellDivider)
amp_ch = ampChargeBase/4;
}
if ( ChargingProc( amp_ch, ampChargeBaseCut, voltBaseMax/cellDivider, 0, false)) {
if ( operatingMode == MODE_BASE_CHARGE)
workStage = SAVE_CHARGE;
else
workStage = SAVE_CHARGE; // в последствии на добивку
nPhase = 0;
}
break;
/************* поддержание заряда АКБ *************/
case SAVE_CHARGE:
switch ( nPhase)
{
case 0: // напряжение упало до voltSaveMin
if ( makePause( 0, voltSaveMin/cellDivider))
nPhase++; // на дозаряд
break;
case 1: // дозаряд
if ( ChargingProc( ampChargeBase/4, 0, voltSaveMax/cellDivider, 0, false))
nPhase++;
break;
case 2: // импульс +
fAllowDisplay = false; // запрет вывода на дисплей
if ( ampCharge > I_MAX - 200 ||
ChargingProc( 200, 0, voltBaseMax/cellDivider, 1000, true)) {
nPhase++;
fAllowDisplay = true;
}
break;
case 3: // импульс -
fAllowDisplay = false; // запрет вывода на дисплей
if ( ampDischarge > I_MAX - 200 ||
DischargingProc( 200, voltSaveMin/cellDivider, 1000, true)) {
nPhase++;
fAllowDisplay = true;
}
break;
case 4: // дозаряд
if ( ChargingProc( ampChargeBase/4, 0, voltSaveMax/cellDivider, 0, true))
nPhase = 0;
break;
}
break;
/************* базовый разряд АКБ *************/
case DISCHARGE_BASE: // разряд батареи до voltDischargeMin
if ( DischargingProc( ampChargeBase, voltDischargeMin/cellDivider, 0, false)){
workStage = STOP; // по окончании останов
nPhase = 0;
}
break;
/************* пульсирующий разряд АКБ *************/
case DISCHARGE_PULSE:
switch ( nPhase)
{
case 0:
if ( voltBattery < voltDischargeMin/cellDivider-5)
workStage = STOP; // напряжение занижено
nPhase++;
break;
case 1: // разряд
if ( !DischargingProc( ampChargeBase, 0, 3000, true)) // разряд не закончен
break;
nPhase++; // закончен
break;
case 2: // пауза
if ( makePause( 3000, 0))
nPhase = 0;
break;
}
break;
/************* перемешивание ???*************/
case CHARGE_BLEND:
static int16_t delta = 0; // для регулировки тока
switch ( nPhase)
{
case 0: // предзаряд CV c отсечкой по току
if ( ChargingProc( ampChargeBase/3, ampChargeBaseCut, voltBaseMax/cellDivider, 0, false))
nPhase++;
break;
case 1: // импульс перемешивания
if ( pwmCharge == 1023 && ampCharge < ampChargeBlend )
delta += 10; // если падает ток импульса при max ШИМ - уменьшить ток
if ( ChargingProc( ampChargeBlend - delta, ampChargeBlend/2, voltBlendMax/cellDivider, 20000, false))
nPhase++;
break;
case 2: // разряд до voltBaseMin - снятие поляризации, обрыв кипения
if ( DischargingProc( ampChargeBlend, voltBaseMin/cellDivider, 0, true))
nPhase++;
break;
case 3: // короткий импульс 5 амп. десульфатации
fAllowDisplay = false; // запретить дисплей
if ( ChargingProc( 500, 0, voltBlendMax/cellDivider, 100, true)) {
nPhase++;
fAllowDisplay = true;
}
break;
case 4: // пауза до падения напряжения до voltSaveMin
if ( voltBattery <= voltSaveMax/cellDivider) {
nPhase++;
}
break;
case 5: // короткий импульс 5 амп. десульфатации
fAllowDisplay = false; // запретить дисплей
if ( ChargingProc( 500, 0, voltBlendMax/cellDivider, 100, true)) {
nPhase++;
nCycles++;
fAllowDisplay = true;
}
break;
case 6: // пауза до падения напряжения до voltSaveMin
if ( voltBattery <= voltSaveMin/cellDivider) {
delta = 0;
nPhase = 0;
}
break;
}
break;
/************* источник питания *************/
case POWER_SUPPLY:
if ( powerSupply( ampChargeBase, voltBaseMax) ||
pwmCharge >1000 )
workStage = STOP;
break;
/************* тест заряд *************/
case CHARGE_TEST:
fAllowDisplay = false; // отключить дисплей
if ( ampCharge > ampChargeBase || // превышение по току
ChargingProc( ampChargeBase +10, 0, voltBlendMax/cellDivider, 5000, true)) {
workStage = STOP;
fAllowDisplay = true;
nPhase = 0;
}
break;
/************* тест разряд *************/
case DISCHARGE_TEST:
fAllowDisplay = false; // отключить дисплей
if ( ampDischarge > ampChargeBase) { // превышение по току
workStage = STOP;
fAllowDisplay = true;
DischargingProc( 0, 0, 0, 0);
break;
}
DischargingProc( ampChargeBase + 10, voltDischargeMin/cellDivider, 5000, true);
break;
/************* тест пульсирующий заряд *************/
case CHARGE_PULSE1:
switch ( nPhase)
{
case 0:
fAllowDisplay = false;
if ( ChargingProc( ampChargeBase, ampChargeBaseCut, voltBaseMax/cellDivider, 300, true)) {
nPhase++;
fAllowDisplay = true;
}
break;
case 1:
if ( makePause( 300, 0))
nPhase = 0;
break;
}
break;
/************* тест пульсирующий заряд *************/
case CHARGE_PULSE2:
switch ( nPhase)
{
case 0:
fAllowDisplay = false; // отключить дисплей
if ( makePause( 20, 0))
nPhase++;
break;
case 1:
if ( ampCharge >ampChargeBase+250) resetChargePWM();
if ( ChargingProc( ampChargeBase, 0, voltBaseMax/cellDivider, 50, true)) { // импульс
nPhase++;
pulseCounter++;
}
break;
case 2:
if ( ampDischarge >ampChargeBase+250) resetDischargePWM();
if ( DischargingProc( ampChargeBase/2, 0, 50, true)) { // импульс разряда
nPhase++;
pulseCounter++;
}
break;
case 3:
if ( pulseCounter > 10) { // после 20 импульсов
fAllowDisplay = true; // включить дисплей
if ( ChargingProc( ampChargeBase, ampChargeBase/3,
voltBaseMax/cellDivider, 20000, true)) { // заряд
pulseCounter = 0; // обнулить счетчик
nPhase++; // на паузу
}
} else nPhase = 0; // продолжить импульсы
break;
case 4:
if ( DischargingProc( ampChargeBase/2, 0, 100, true)) // разряд
nPhase++;
break;
case 5:
if ( makePause( 2000, 0))
nPhase++;
break;
case 6:
if ( makePause( 100000, voltSaveMin/cellDivider))
nPhase= 0;
break;
}
break;
/************* установка PWM токов *************/
case MEASURE_PWM:
switch ( nPhase)
{
case 0:
fAllowDisplay = false; // отключить дисплей
if ( ampCharge > ampChargeBase) { // превышение по току
fAllowDisplay = true;
nPhase ++;
ChargingProc( 0, 0, 0, 0, 0);
break;
}
ChargingProc( ampChargeBase + 10, 0, voltBlendMax/cellDivider, 5000, true);
break;
case 1:
if ( makePause( 2000, 0))
nPhase++;
break;
case 2:
fAllowDisplay = false; // отключить дисплей
if ( ampDischarge > ampChargeBase) { // превышение по току
nPhase ++;
fAllowDisplay = true;
DischargingProc( 0, 0, 0, 0);
break;
}
DischargingProc( ampChargeBase + 10, 100, 10000, true);
break;
case 3:
if ( makePause( 2000, 0))
workStage = nextWorkStage; // на следующую стадию
break;
}
break;
}
/**************************************************************/
if ( prevWorkStage != workStage)
prevWorkStage = workStage; // сохранение предыдущей фазы работы
dt = millis() - dt; // замер длительность loop
// if ( dt > 5)
{ // вывод в сериал для лога и отладки
// Serial << F("dT ") << dt << '\n';
if ( voltBattery > 300)
Serial <<voltBattery<<' '<<ampCharge/10 + 1200<<' '<<1200+ampDischarge/10<<' '<<1200+ampChargeBase/10<<' '<<pwmCharge/10+1200<< ' '<<1200-pwmDischarge/10<<' '<<dt<<'\n';
else
Serial <<voltBattery<<' '<<ampCharge<<' '<<ampDischarge<<' '<<ampChargeBase<<'\n';
}
dt = millis();
}
/*****************************************************
* Подсчет полученной, отданной энергии в ампер часах
* calcEnergy() каждую секунду
*****************************************************/
void calcEnergy(){
static uint32_t prevTimeCalcEnergy = 0;
if ( onTimeSec > prevTimeCalcEnergy) {
prevTimeCalcEnergy = onTimeSec;
chargeEnergy += ((float)ampCharge) / 360000.0;
dischargeEnergy += ((float)ampDischarge) / 360000.0;
}
}
/*****************************************************
* сделать паузу по времени или падению до напряжения
* bool makeРause(uint32_t tpause, int16_t voltage)
* tpause - время паузы в милисекундах
* voltage - по падению до напряжения, приоритетно
* return false - пауза не окончена
* true - пауза закончилась
****************************************************/
bool makePause( uint32_t tpause, int16_t voltage)
{
static uint32_t tpStart = 0;
fPauseCompleted = false;
if ( voltBattery >= voltage){ // если напряжение не упало ниже voltage
if ( tpause) { // если установлена длительность
if ( !tpStart) tpStart = millis();
if (( millis() - tpStart) >= tpause) { // если вышло время
tpStart = 0;
fPause = false;
fPauseCompleted = true;
return true; // пауза окончена
} else {
fPause = true;
return false;
}
}
fPause = true;
return false;
} else {
tpStart = 0;
fPause = false;
fPauseCompleted = true;
return true;
}
}
/*************************************************************************************
* Заряд аккумулятора СС - постоянным током до достижения voltage на аккумуляторе.
* Если cutcurrent не равен 0, то после достижения voltage, заряд CV - падающим током до cutcurrent
* В любом случае, если установлена длительность заряда, окончание по прывышению длительности
*
* bool ChargingProc( uint16_t current, uint16_t cutcurrent, uint16_t maxvoltage, uint32_t duration)
* current - ток заряда, если 0 - прекращение заряда
* cutcurrent - min ток прекращения заряда, если 0 - не учитывается, отсечка по voltage
* voltage - предел заряда по напряжению
* duration - макс длительность заряда в милисекундах, если 0 - не учитывается
* pulse - если true, резкий фронт нарастания, pwmCharge ,берется из chargePWM[]
* return false - заряд не окончен
* true - заряда закончен
* устанавливает глоб. флаги
* fChargeCompleted = true - завершение по напряжению, длительности или по отсечке тока
* fCharge = true - заряд идет
* глобальные значения
* voltBattery, ampCharge
* примеры !!! следить за сочетанием параметров
* ChargingProc( 100, 50, 1460, 0, false)
* ChargingProc( 100, 0, 1460, 0, true)
* ChargingProc( 100, 0, 1460, 2000, true)
* ChargingProc( 100, 50, 1460, 2000, false)
****************************************************/
bool ChargingProc( uint16_t current, uint16_t cutcurrent, uint16_t maxvoltage, uint32_t duration, bool pulse)
{
uint16_t dPwm; // дельта приращения pwm
int16_t temp_cur; // временная переменная
static bool fCV = false; // флаг CV - поддержания напряжения, до cutcurrent
static bool fStartPulse = true; // начало импульса
static uint32_t start_time = 0; // время начала фазы заряда
static uint32_t charge_duration = 0; // прошло времени
fChargeCompleted = false;
fStartPulse ;
if ( duration){
if ( !start_time)
start_time = onTimeMill; // установка времени начала фазы заряда
charge_duration = duration + start_time - onTimeMill;
}
/* завершение если .. */
if ( current == 0 || fPause || fDischarge ||
charge_duration > duration ||
( voltBattery > maxvoltage && cutcurrent == 0 )) {
L_EndCharge: // окончание заряда
fChargeCompleted = true;
start_time = 0;
charge_duration = 0;
pwmCharge = 0;
fCV = false;
digitalWrite10( pwmCharge); // ШИМ = 0
fCharge = false;
fStartPulse = true;
return true;
}
/******** вычисление приращения ШИМ ********/
if ( ampCharge <= current+current/10 && ampCharge >= current-current/10)
dPwm = 1;
else if ( pwmCharge < 200) dPwm = 5;
else if ( pwmCharge < 500) dPwm = 2;
else dPwm = 1;
/*********************************************/
if ( voltBattery >= maxvoltage) // напряжение достигнуто
{
fCV = true; // в режим CV
if ( ampCharge > cutcurrent) { // ток отсечки не достигнут
if ( pwmCharge > dPwm) {
pwmCharge -= dPwm; // уменьшение тока заряда
goto L_ContCharge; // продолжить заряда
}
} else
goto L_EndCharge; // закончить заряд
}
/******************* заполнение массива chargePWM *********************/
temp_cur = (ampCharge+10)/100*100;
if ( ampCharge > 0 && ampCharge < 50) // для малых токов
chargePWM[0] = pwmCharge;
else if ( temp_cur && ampCharge > temp_cur-20 && ampCharge < temp_cur + 10)
chargePWM[temp_cur/100] = pwmCharge;
/********************* если импульс и начало его *********************/
if ( pulse && fStartPulse) {
if ( current < 1200) {
temp_cur = current/100; // индекс массива PWM
while ( temp_cur >= 0) { //
pwmCharge = chargePWM[temp_cur]; //
if ( !pwmCharge ) // если нет значения в массиве по текущему индексу
temp_cur--; // берется предыдущий
else
break; // выход из цикла если найден
}
}
fStartPulse = false; //
}
if ( voltBattery < maxvoltage && fCV) // в режиме CV
pwmCharge += dPwm;
if ( !fCV) { // в режиме CС
if ( ampCharge < current) {
if ( pwmCharge < 1023) pwmCharge += dPwm;
}
else
{
if ( ampCharge > current)
if ( pwmCharge > dPwm) pwmCharge -= dPwm;
}
}
L_ContCharge:
fCharge = true;
//fStartPulse = false;
pwmWrite10( pwmCharge); // установка ШИМ
return false;
}
/*****************************************************
* как источник питания
* bool powerSupply( uint16_t current, uint16_t voltage)
* current - ток, voltage - напряжение
******************************************************/
bool powerSupply( uint16_t current, uint16_t voltage) {
if ( ampCharge > 600 || !current || !voltage) { // превышение по току или неверные параметры
pwmCharge = 0;
pwmWrite10( pwmCharge);
digitalWrite10( pwmCharge); // ШИМ = 0
fChargeCompleted = true;
fCharge = false;
return true; // завершено
}
if ( ampCharge > current || voltBattery > voltage)
pwmCharge--;
else
pwmCharge++;
fChargeCompleted = false;
fCharge = true;
pwmWrite10( pwmCharge);
return false;
}
/*****************************************************
* Разряд аккумулятора постоянным током current
* bool DischargingProc( uint16_t current, uint16_t voltage, uint32_t duration, bool pulse)
* current - ток разряда Амперы
* voltage - предел разряда по напряжению
* duration - макс длительность разряда в милисекундах, если 0 - не учитывается
* pulse - если true, резкий фронт нарастания, pwmDischarge ,берется из dischargePWM[]
* return
* false - разряд продолжается
* true - разряд по условию закончен
* устанавливает глоб. флаги
* fDischargeCompleted = true завершение по напряжению
* fDischarge = true - разряд идет
* примеры
* DischargingProc( 100, 1200, 2000) завершение по напряжению и длительности
* DischargingProc( 100, 0, 2000) завершение по длительности
* DischargingProc( 100, 1200, 0) завершение по напряжению
*****************************************************/
bool DischargingProc( uint16_t current, uint16_t voltage, uint32_t duration, bool pulse)
{
uint16_t dPwm; // дельта приращения pwm
int16_t temp_cur; // временная переменная
static uint32_t start_time = 0; // время начала фазы разряда
static uint32_t dischg_duration = 0; // прошло времени в разряде
static bool fStartPulse = true; // начало импульса
fDischargeCompleted = false;
if ( duration){ // если указана длительность
if ( !start_time)
start_time = onTimeMill; // установка времени начала фазы заряда
dischg_duration = duration + start_time - onTimeMill;
}
/* завершение если .. -> останов разряда */
if ( current == 0 || voltBattery < voltage || dischg_duration > duration || fPause || fCharge) {
start_time = 0;
dischg_duration = 0;
pwmDischarge = 0;
digitalWrite9( pwmDischarge); // ШИМ = 0
fDischargeCompleted = true;
fDischarge = false;
fStartPulse = true;
return true;
}
/************ вычисление приращения ШИМ *********/
if ( !fDischarge)
pwmDischarge = 760; // !!! смещение для открытия MOSFET разряда
if ( ampDischarge <= current+current/10 && ampCharge >= current-current/10)
dPwm = 1;
else if ( pwmDischarge < 200) dPwm = 30;
else if ( pwmDischarge < 500) dPwm = 20;
else if ( pwmDischarge < 700) dPwm = 2;
else dPwm = 1;
/****************** регулировка ******************/
if ( ampDischarge < current) {
if ( pwmDischarge < 1023) pwmDischarge += dPwm;
} else {
if ( ampDischarge > (current + 2))
if ( pwmDischarge > dPwm) pwmDischarge -= dPwm;
}
/******* заполнение массива dischargePWM[] ******/
if ( ampDischarge > 30 && ampDischarge < 60) // для малых токов
dischargePWM[0] = pwmDischarge;
temp_cur = (ampDischarge+10)/100*100;
if ( temp_cur && ampDischarge > temp_cur-10 && ampDischarge < temp_cur + 10)
dischargePWM[temp_cur/100] = pwmDischarge;
/********** если импульс и начало его ************/
if ( pulse && fStartPulse) {
if ( current < 1200) {
temp_cur = current/100;
while ( temp_cur >= 0) {
pwmDischarge = dischargePWM[temp_cur];
if ( !pwmDischarge )
temp_cur--;
else
break;
}
}
fStartPulse = false;
}
/******************************************/
fDischarge = true;
pwmWrite9( pwmDischarge);
return false;
}
/*****************************************************
* getVoltageCurrent() - считать текущие напряжение и токи
* запись в глобальные переменные voltBattery ampCharge ampDischarg
*****************************************************/
void getVoltageCurrent(){
uint16_t buf;
uint32_t chg_cur, dischg_cur;
{ // замер напряжение
buf = 0;
for (int i=0; i <= 24; i++) {
buf += analogRead( pinADC_Voltage);
}
buf = buf / 24;
voltBattery = ((uint32_t)buf) * 25 /10;
}
{ // замер тока заряда
buf = 0;
for (int i=0; i <= 16; i++) {
buf += analogRead( pinADC_ChargeCurrent);
}
buf = buf / 16;
ampCharge = buf * 3; // шунт - 3 резистора 0.1 в параллель
if ( ampCharge)
ampCharge += 5;
}
{ // ток разряда
buf = 0;
for (int i=0; i <= 16; i++) {
buf += analogRead( pinADC_DischargeCurrent);
}
buf = buf / 16;
ampDischarge = buf * 2; // шунт - 2 резистора 0.1 в параллель
if ( ampDischarge > 100)
ampDischarge = ampDischarge*9/10 + 10;
else if ( ampDischarge > 0 && ampDischarge < 100)
ampDischarge = ampDischarge*94/100 + 5;
}
if ( voltBattery > 50 && voltBattery < 2000) voltBattery += (2000-voltBattery)/220; // поправка для низких напряжений
voltBattery -= -ampDischarge/10 + ampCharge/16; // поправка падения U на шунтах и проводах
}
/*****************************************************
* getTimeWork() - заполнение временных меток
*****************************************************/
void getTimeWork()
{
static bool fFirstStart = true; // флаг начала сеанса
static uint32_t timeStart = 0; // время старта сеанса работы
static uint32_t timePrev = 0; // длительность предыдущего сеанса
onTimeMill = millis(); // время от включения ЗУ в милисекундах
onTimeSec = onTimeMill / 1000ul; // время от включения ЗУ в секундах
if ( (workStage != STOP) && fFirstStart) { // если не STOP и первый запуск в сеансе
fFirstStart = false; // отметка
timeStart = onTimeMill;
}
if ( workStage != STOP) { // если не в стопе
TimeWork = (onTimeMill - timeStart) / 1000ul + timePrev; // секунд в работе
} else {
if ( prevWorkStage != STOP) // если пред не стоп сохр. длит.
timePrev = TimeWork;
timeStart = 0;
fFirstStart = true;
}
timeHours = (TimeWork / 3600ul); // часов в работе
timeMins = (TimeWork % 3600ul) / 60ul; // минут
timeSecs = (TimeWork % 3600ul) % 60ul; // секунд
}
/*****************************************************
* displayFrame( frame, marked) вывод на дисплей
* frame - номер фрейма
* marked - помеченная строка, 0 - без метки
*****************************************************/
void displayFrame( uint8_t frame, uint8_t marked)
{
static unsigned long tDisplay; // время вывода на дисплей
char bufStr[12]; // буфер формирования строки вывода
unsigned long ttt = millis();
if ( (ttt - tDisplay) >= 263) // период обновление экрана
tDisplay = ttt; // обновить
else
return;
display.setTextSize( 2);
display.clearDisplay();
display.setCursor(0,0);
if ( frame == FRAME_INFO) { // основной рабочий-информационный экран
uint16_t voltInfo; //
int16_t current = ( ampCharge > ampDischarge ? ampCharge : -ampDischarge);
// uint16_t voltage = voltBattery;
if ( workStage == DISCHARGE_BASE)
voltInfo = voltDischargeMin/cellDivider;
else if ( workStage == CHARGE_BLEND)
voltInfo = voltBlendMax/cellDivider;
else
voltInfo = voltBaseMax; // /cellDivider;
/* округление до десятых */
// if ( current > 10 )
// current % 10 > 4 ? current = current/10*10+10 : current = current/10*10;
// else if ( current < -10 )
// current % 10 < -4 ? current = current/10*10-10 : current = current/10*10;
//if ( voltage > 10 )
// voltage % 10 > 4 ? voltage = voltage/10*10+10 : voltage = voltage/10*10;
sprintf(bufStr, "%2d.%02dv%2d.%1d", voltBattery/100, (voltBattery%100), voltInfo/100, (voltInfo%100)/10 );
display.print(bufStr);
display.setCursor( 0, 16);
sprintf(bufStr, "%2d.%02da%2d.%1d", current/100, (abs(current)%100), ampChargeBase/100, (abs(ampChargeBase)%100)/10);
display.println(bufStr);
display.setCursor( 0, 32);
uint32_t dt = ( onTimeSec + 1)%5;
if ( dt == 1 || dt == 2 ) { // отображение 2 секунды
if ( operatingMode == MODE_1CELL) display.print ( F("CELL "));
if ( workStage == STOP) display.println( F("STOP"));
else if ( workStage == ACTIVATION) display.print ( F("ACTIV"));
else if ( workStage == CHARGE_ASSYMETRIC) display.print ( F("ASMCH"));
else if ( workStage == CHARGE_BASE || workStage == CHARGE_TEST)
display.println( F("CHARG"));
else if ( workStage == CHARGE_BLEND) display.print ( F("BLEND"));
else if ( workStage == SAVE_CHARGE ) display.println( F("SAVE"));
else if ( workStage == POWER_SUPPLY) display.print ( F("POWER"));
else if ( workStage == CHARGE_PULSE1 || workStage == CHARGE_PULSE2)
display.print ( F("PULSE"));
else if ( workStage == DISCHARGE_BASE || workStage == DISCHARGE_TEST) {
display.println( F("DISCH"));
display.print ( cycleCounter+1);
}
sprintf(bufStr, "%3d:%02d:%02d", timeHours, timeMins, timeSecs);
display.print(bufStr);
}
else if ( dt == 3 || dt == 4) { // полученная отданная емкость
sprintf(bufStr, "+ %d.%02dah", (int)(chargeEnergy),(int)(chargeEnergy*100)%100);
display.println(bufStr);
sprintf(bufStr, "- %d.%02dah", (int)(dischargeEnergy),(int)(dischargeEnergy*100)%100);
display.println(bufStr);
sprintf(bufStr, "Pwm %04d", pwmCharge > pwmDischarge ? pwmCharge : -pwmDischarge);
display.println(bufStr);
} else { //
display.setCursor( 0, 48);
sprintf(bufStr, "Pwm %04d", pwmCharge > pwmDischarge ? pwmCharge : -pwmDischarge);
display.println(bufStr);
}
}
else if ( frame == FRAME_START_STOP) { // экран запроса СТАРТ/СТОП
sprintf(bufStr, "Vb %02d.%02dv", voltBattery/100, voltBattery%100);
display.println(bufStr);
display.setTextSize( 3);
display.setCursor(0,24);
if ( workStage != STOP) display.println( F(" STOP?"));
else display.println( F(" START?"));
}
else if ( frame == FRAME_MODE) { // экран запроса режима работы
if ( markedLineMode < 5) {
display.println( F(" Auto"));
display.println( F(" Charge "));
display.print ( F(" DisCharge"));
display.print ( F(" Power"));
display.setCursor( 0, ( markedLineMode -1 ) *16);
} else if ( markedLineMode < 9) {
display.println( F(" Pulse1"));
display.println( F(" Pulse2"));
display.println( F(" ..."));
display.print ( F(" ..."));
display.setCursor( 0, ( markedLineMode -5 ) *16);
}
display.print( F("> "));
}
else if ( frame == FRAME_VOLTAGE) { // экран установок напряжений - 6 параметров
display.setTextSize( 1);
display.print( F(" Uact_min ")); display.println( (float)voltActivationMin / 100.0);
display.print( F(" Uact_max ")); display.println( (float)voltActivationMax / 100.0);
display.print( F(" Ubase_min ")); display.println( (float)voltBaseMin / 100.0);
display.print( F(" Ubase_max ")); display.println( (float)voltBaseMax / 100.0);
display.print( F(" Urepair ")); display.println( (float)voltBlendMax / 100.0);
display.print( F(" Usave ")); display.println( (float)voltSaveMin / 100.0);
display.print( F(" Urazr_min ")); display.println( (float)voltDischargeMin / 100.0);
display.println( F(" ..."));
display.setCursor( 0, ( marked -1 ) *8);
if ( fEdit) display.print( F("* "));
else display.print( F("> "));
}
else if ( frame == FRAME_CURRENT) { // экран установок токов - 8 параметров
display.setTextSize( 1);
display.print( F(" Iactivat ")); display.println( (float)ampChargeActivation / 100.0);
display.print( F(" Ichg_base ")); display.println( (float)ampChargeBase / 100.0);
display.print( F(" Icut_base ")); display.println( (float)ampChargeBaseCut / 100.0);
display.print( F(" Irepair ")); display.println( (float)ampChargeBlend / 100.0);
display.print( F(" Isave ")); display.println( (float)ampChargeSave / 100.0);
display.print( F(" Irazryad ")); display.println( (float)ampDischargeMan / 100.0);
display.println( F(" ..."));
display.print( F(" ..."));
display.setCursor( 0, ( marked -1 ) *8);
if ( fEdit) display.print( F("* "));
else display.print( F("> "));
}
else if ( frame == FRAME_DURATION) { // экран установок длительностей - 8 параметров
display.setTextSize( 1);
display.print( F(" Tact_charg ")); display.println( (float)durActivationCharge / 1000.0);
display.print( F(" Tact_pause ")); display.println( (float)durActivationPause / 1000.0);
display.print( F(" Tass_charg ")); display.println( (float)durAssCharge / 1000.0);
display.print( F(" Tass_dischg ")); display.println( (float)durAssDischarge / 1000.0);
display.print( F(" Tass_pause ")); display.println( (float)durAssPause / 1000.0);
display.println( F(" ..."));
display.println( F(" ..."));
display.print( F(" ..."));
display.setCursor( 0, ( marked -1 ) *8);
if ( fEdit) display.print( F("* "));
else display.print( F("> "));
}
display.display();
}
/****************************************************
* uint8_t getButtonState() опрос кнопок
* возврат кода нажатой кнопки - порт D
* при долгом нажатии - повтор
*****************************************************/
uint8_t getButtonState()
{
static uint32_t pressTime = 0; // время нажатия кнопки
static uint8_t button; // код кнопки
static bool fSend0 = false; // отправлено в четную полусекунду
static bool fSend1 = false; // отправлено в нечетную полусекунду
uint8_t state = (~PIND & 0b1111100) >> 2; // на порту D, состояние, код кнопки
if ( state && !pressTime) // начало нажатия
{
pressTime = onTimeMill; // время начала нажатия
button = state; // код кнопки
}
if ( !state && pressTime) // если кнопка отпущена
{
if ( onTimeMill - pressTime > 80)
{
// Serial << onTimeMill - pressTime<< '\n';
pressTime = 0;
fSend0 = false;
fSend1 = false;
if ( onTimeMill - pressTime < 1500)
return 0;
return button;
}
}
if ( state && pressTime && ( onTimeMill - pressTime) > 1000) { // длительное нажатие - повтор
if ( (onTimeMill/200+1)%2) { // нечетная доля
fSend0 = false;
if ( !fSend1) { // и не отправлено
fSend1 = true;
return button; // возврат кода кнопки
} else
return 0;
} else { // четная доля
fSend1 = false;
if ( !fSend0) { // и не отправлено
fSend0 = true;
return button; // возврат кода кнопки
} else return 0; //
}
}
return 0;
}
/*****************************************************
* Обработка по нажатию кнопок
* void procPresButtons()
*****************************************************/
void procPresButtons()
{
uint8_t button = getButtonState();
switch ( button)
{
case BUT_OK:
if ( curFrame == FRAME_INFO) { // переход на выбор режима работы с последующим стартом или останов
if ( workStage != STOP) curFrame = FRAME_START_STOP; // если в работе в окно старт\стоп
else curFrame = FRAME_MODE; // если в останове - > выбор режима работы
}
else if ( curFrame == FRAME_START_STOP) { // если в окне старт\стоп
if ( workStage != STOP) // и в работе
{
workStage = STOP; // в останов
nPhase = 0;
} else {
cycleCounter = 0; // по старту сброс счетчика тренировок
if ( markedLineMode == 1) { // если MODE_AUTO_CHARGE
operatingMode = MODE_AUTO_CHARGE;
workStage = ACTIVATION; // в фазу активации батареи
}
if ( markedLineMode == 2) { // MODE_BASE_CHARGE
operatingMode = MODE_BASE_CHARGE;
workStage = CHARGE_BASE;
}
if ( markedLineMode == 3) { // MODE_DISHARGE
operatingMode = MODE_DISHARGE;
workStage = DISCHARGE_BASE;
}
if ( markedLineMode == 4) { // MODE_POWER
operatingMode = MODE_POWER;
workStage = POWER_SUPPLY;
}
if ( markedLineMode == 5) { //
operatingMode = MODE_NORMAL;
workStage = MEASURE_PWM;
nextWorkStage = CHARGE_PULSE1;
}
if ( markedLineMode == 6) { //
operatingMode = MODE_NORMAL;
workStage = MEASURE_PWM;
nextWorkStage = CHARGE_PULSE2;
}
resetChargePWM();
resetDischargePWM();
nPhase = 0;
}
curFrame = FRAME_INFO;
}
else if ( curFrame == FRAME_MODE)
curFrame = FRAME_START_STOP;
else if ( curFrame == FRAME_VOLTAGE || curFrame == FRAME_CURRENT || curFrame == FRAME_DURATION)
fEdit = !fEdit; // установить флаг правки параметров
break;
case BUT_RIGHT: // переход между окнами и коррекция Umax
case BUT_LEFT:
if ( workStage == STOP && !fEdit) {
markedLineParam = 1;
if ( curFrame == FRAME_START_STOP || curFrame == FRAME_MODE)
curFrame = FRAME_INFO;
else if ( button == BUT_RIGHT) curFrame++;
else curFrame--;
if ( curFrame < FRAME_INFO) curFrame = FRAME_DURATION;
if ( curFrame > FRAME_DURATION) curFrame = FRAME_INFO;
}
else if ( curFrame == FRAME_START_STOP) curFrame = FRAME_INFO; //
else if ( workStage == CHARGE_BLEND) {
button == BUT_RIGHT ? voltBlendMax += 10 : voltBlendMax -= 10;
if ( voltBlendMax < 1470) voltBlendMax = 1470;
else if ( voltBlendMax > 1670) voltBlendMax = 1670;
} else if ( workStage == DISCHARGE_BASE || workStage == DISCHARGE_PULSE){
button == BUT_RIGHT ? voltDischargeMin += 10 : voltDischargeMin -= 10;
if ( voltDischargeMin < 1050) voltDischargeMin = 1050;
else if ( voltDischargeMin > 1280) voltDischargeMin = 1280;
} else if ( workStage != STOP){
button == BUT_RIGHT ? voltBaseMax += 10 : voltBaseMax -= 10;
if ( voltBaseMax < 200) voltBaseMax = 200;
else if ( voltBaseMax > 1700) voltBaseMax = 1700;
}
break;
case BUT_UP: // если не редактирование движение по пунктам окна
case BUT_DOWN: // вверх вниз
if ( fEdit) { // если редактирование - ручная правка базовых параметров
if ( curFrame == FRAME_VOLTAGE){ // если окно настройки напряжений
if ( markedLineParam == 1){ // напряжение активации минимальное
button == BUT_UP ? voltActivationMin += 10 : voltActivationMin -= 10;
if ( voltActivationMin < 600) voltActivationMin = 600;
else if ( voltActivationMin > 1200) voltActivationMin = 1200;
}
if ( markedLineParam == 2){ // напряжение окнчания активации
button == BUT_UP ? voltActivationMax += 10 : voltActivationMax -= 10;
if ( voltActivationMax < 1000) voltActivationMax = 1000;
else if ( voltActivationMax > 1240) voltActivationMax = 1240;
}
if ( markedLineParam == 3){ // напряжение снижения тока для base заряда
button == BUT_UP ? voltBaseMin += 10 : voltBaseMin -= 10;
if ( voltBaseMin < 1300) voltBaseMin = 1300;
else if ( voltBaseMin > 1420) voltBaseMin = 1420;
}
if ( markedLineParam == 4){ // максимальное напряжение для base заряда
button == BUT_UP ? voltBaseMax += 10 : voltBaseMax -= 10;
if ( voltBaseMax < 1380) voltBaseMax = 1380;
else if ( voltBaseMax > 1470) voltBaseMax = 1470;
}
if ( markedLineParam == 5){ // максимальное напряжение перемешивания
button == BUT_UP ? voltBlendMax += 10 : voltBlendMax -= 10;
if ( voltBlendMax < 1470) voltBlendMax = 1470;
else if ( voltBlendMax > 1670) voltBlendMax = 1670;
}
if ( markedLineParam == 6){ // напряжение хранения минимальное
button == BUT_UP ? voltSaveMin += 10 : voltSaveMin -= 10;
if ( voltSaveMin < 1280) voltSaveMin = 1280;
else if ( voltSaveMin > 1340) voltSaveMin = 1340;
}
if ( markedLineParam == 7){ // минимальное напряжение для разряда
button == BUT_UP ? voltDischargeMin += 10 : voltDischargeMin -= 10;
if ( voltDischargeMin < 1050) voltDischargeMin = 1050;
else if ( voltDischargeMin > 1280) voltDischargeMin = 1280;
}
}
if ( curFrame == FRAME_CURRENT){ // окно настройки токов
if ( markedLineParam == 1){ // ток заряда активации
button == BUT_UP ? ampChargeActivation += 10 : ampChargeActivation -= 10;
if ( ampChargeActivation < 20) ampChargeActivation = 20;
else if ( ampChargeActivation > 500) ampChargeActivation = 500;
}
if ( markedLineParam == 2){ // ток основного заряда
button == BUT_UP ? ampChargeBase += 10 : ampChargeBase -= 10;
if ( ampChargeBase < 10) ampChargeBase = 10;
else if ( ampChargeBase > 900) ampChargeBase = 900;
}
if ( markedLineParam == 3){ // ток отсечки заряда CV
button == BUT_UP ? ampChargeBaseCut += 10 : ampChargeBaseCut -= 10;
if ( ampChargeBaseCut < 10) ampChargeBaseCut = 10;
else if ( ampChargeBaseCut > 200) ampChargeBaseCut = 200;
}
if ( markedLineParam == 4){ // ток заряда REPAIR
button == BUT_UP ? ampChargeBlend += 10 : ampChargeBlend -= 10;
if ( ampChargeBlend < 10) ampChargeBlend = 10;
else if ( ampChargeBlend > 800) ampChargeBlend = 800;
}
if ( markedLineParam == 5){ // ток хранения
button == BUT_UP ? ampChargeSave += 10 : ampChargeSave -= 10;
if ( ampChargeSave < 10) ampChargeSave = 10;
else if ( ampChargeSave > 200) ampChargeSave = 200;
}
if ( markedLineParam == 6){ // ток разряда
button == BUT_UP ? ampDischargeMan += 10 : ampDischargeMan -= 10;
if ( ampDischargeMan < 10) ampDischargeMan = 10;
else if ( ampDischargeMan > 900) ampDischargeMan = 900;
}
if ( markedLineParam == 7){ // число циклов тренировки
button == BUT_UP ? nCycles++ : nCycles--;
if ( nCycles < 1) nCycles = 1;
else if ( nCycles > 10) nCycles = 10;
}
}
if ( curFrame == FRAME_DURATION){ // окно длительностей
if ( markedLineParam == 1){ // длительность фазы заряда при активации
button == BUT_UP ? durActivationCharge += 100 : durActivationCharge -= 100;
if ( durActivationCharge < 100) durActivationCharge = 100;
else if ( durActivationCharge > 60000) durActivationCharge = 60000;
}
if ( markedLineParam == 2){ // длительность паузы при активации
button == BUT_UP ? durActivationPause += 100 : durActivationPause -= 100;
if ( durActivationPause < 100) durActivationPause = 100;
else if ( durActivationPause > 60000) durActivationPause = 60000;
}
if ( markedLineParam == 3){ // длительность фазы заряда в ассиметричном заряде
button == BUT_UP ? durAssCharge += 100 : durAssCharge -= 100;
if ( durAssCharge < 100) durAssCharge = 100;
else if ( durAssCharge > 60000) durAssCharge = 60000;
}
if ( markedLineParam == 4){ // длительность фазы разряда в ассиметричном заряде
button == BUT_UP ? durAssDischarge += 100 : durAssDischarge -= 100;
if ( durAssDischarge < 100) durAssDischarge = 0;
else if ( durAssDischarge > 60000) durAssDischarge = 60000;
}
if ( markedLineParam == 5){ // длительность паузы в ассиметричном заряде
button == BUT_UP ? durAssPause += 100 : durAssPause -= 100;
if ( durAssPause < 100) durAssPause = 100;
else if ( durAssPause > 60000) durAssPause = 60000;
}
}
}
if ( curFrame == FRAME_INFO) { // изменение основного тока
button == BUT_UP ? ampChargeBase += 10 : ampChargeBase -= 10;
if ( ampChargeBase < 10) ampChargeBase = 10;
else if ( ampChargeBase > 700) ampChargeBase = 700;
break;
}
// 8 пунктов в окне настроек
if ( curFrame > FRAME_INFO && curFrame < FRAME_MODE && !fEdit) {
button == BUT_UP ? markedLineParam-- : markedLineParam++;
if ( !markedLineParam) markedLineParam = 8;
else if ( markedLineParam > 8) markedLineParam = 1;
} // 8 пунктов в окне выбора режимов работы
else if ( curFrame == FRAME_MODE) {
button == BUT_UP ? markedLineMode-- : markedLineMode++;
if ( !markedLineMode) markedLineMode = 8;
else if ( markedLineMode > 8) markedLineMode = 1;
}
break;
}
}
/*****************************************************
* Индиация фазы работы RGB светодиодом
* инверсия сигналов
*****************************************************/
void ledIndication()
{
uint8_t dt = ( onTimeSec + 1)%2;
switch ( workStage)
{
case STOP: // останов - белый
case ANALYSIS_CONDITION: //
case ANALYSIS_CHARGE:
case ANALYSIS_DISCHARGE:
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 0);
break;
case ACTIVATION: // активация - желтый мигающий
if ( dt ) {
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
} else {
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
}
break;
case CHARGE_PULSE1:
case CHARGE_PULSE2: // импульсы заряда - красный - желтый
if ( dt ) {
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
} else {
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
}
break;
case CHARGE_ASSYMETRIC: // ассиметричный заряд - красный синий
if ( dt ) {
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
} else {
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 0);
}
break;
case CHARGE_TEST: // основной заряд - красный
case CHARGE_BASE:
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
break;
case CHARGE_BLEND: // восстановление - зеленый мигающий
if ( dt ) {
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
} else {
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
}
break;
case SAVE_CHARGE: // хранение - зеленый
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
break;
case DISCHARGE_BASE: // разряд - синий
case DISCHARGE_TEST:
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 0);
break;
case POWER_SUPPLY: // БП - желтый
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
break;
}
}
/*****************************************************
* Сброс массива значений PWM для токов
*****************************************************/
void resetChargePWM( void){
for ( uint8_t i=1; i<12; i++) {
chargePWM[i] = 0;
}
return;
}
void resetDischargePWM( void){
for ( uint8_t i=1; i<12; i++) {
chargePWM[i] = 0;
}
return;
}
Касательно гелевых и AGM АКБ - это всё те же свинцово-кислотные аккумуляторы, с присущей им некоторой спецификой обслуживания и параметров заряда. В сети имеется необходимая по этому поводу информация.
P.S. БУДЬТЕ ВНИМАТЕЛЬНЫ И ОСТОРОЖНЫ ПРИ РАБОТЕ С ПРОБЛЕМНЫМИ АКБ!!!
Вот казалось бы 10 лет АКБ - ему жить и жить, но режима перемешивания электролита при 16,2 вольта при больших импульсных токах (средневыпрямленный ток небольшой), не выдержал. Хорошо обошлось без травм и химических ожогов, но обстановку в помещении попортил.. Правда присутствовал некоторый локальный нагрев в верхней части 4 банки, наверно локальное неполное КЗ.
Так что вот, берегите себя и окружающих!!!
Не удалось избавиться от самовозбуждения разрядного мосфета.
Который изрядно при этом нагревался, заставляя вентилятор набирать обороты.
Поставил в разрядную цепь мощный биполярный транзистор (Ik>10Ампер, h21э>20).
Теперь всё работает как часы :)
/**********************************************************************************
* !!! для аппаратной версии с конденсаторами и биполярным разрядным транзистором
* --------------------------------------------------------
* !!! Версия для свинцовых 12V аккумуляторов,
* режимы: автозаряд, ручной базовый без выкрутасов, разряд,
* блок питания с регулировкой тока и напряжение
*---------------------------------------
* Gyver core atmega328 optiboot 8
* !!! Если не GyverCore, закоментировать analogPrescaler( 64);
* вывод в serial 115200 бод
*---------------------------------------
* !!! В скетче все наряжения и токи в сотых долях, т.е. U 1240 = 12.40V, I 150 = 1.5A
* !!!
* В доработку:
* графика в меню
* анализ состояния
* расширить функциональность, добавка режимов
* сохранение параметров в EEPROM ???
*---------------------------------------
* !!! В терминале все команды через STOP - s
*---------------------------------------
* !!! В режиме стоп кнопки вправо влево - в меню настроек, вверх вниз выбор параметра
* ОК и вверх вниз ОК - правка параметра
* !!! В рабочем режиме - вверх вниз - коррекция тока, вправо влево - напряжения
*---------------------------------------
* !!! После разряда автоматом включается заряд
*********************************************************************************/
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// https://arduino.ru/forum/programmirovanie/shim-proizvolnogo-razresheniya...
#include "FlexPWM.h"
/* http://arduino.ru/forum/programmirovanie/etyudy-dlya-nachinayushchikh-po... */
template <typename T> inline HardwareSerial & operator << (HardwareSerial &s, T n) { s.print(n); return s; }
/********************* Пины ********************************/
#define pinLedRed 13
#define pinLedGreen 12
#define pinLedBlue 11
#define pinLeftButton 2
#define pinRightButton 3
#define pinOkButton 4
#define pinDownButton 5
#define pinUpButton 6
#define pinADC_Voltage A1
#define pinADC_ChargeCurrent A0
#define pinADC_DischargeCurrent A2
#define pinPwmCharge 10
#define pinPwmDischarge 9
/****************** кнопки коды **************************/
#define BUT_OK 4
#define BUT_RIGHT 2
#define BUT_LEFT 1
#define BUT_UP 16
#define BUT_DOWN 8
/****************** ток **************************/
#define I_MAX 800 // ограничение по току xx ампер
/***************** тип аккумулятора **********************/
#define BAT_PBSB 0 // сурьма - классика
#define BAT_PBCA 1 // гибрид
#define BAT_CACA 2 // кальций кальций
uint8_t batteryType = BAT_CACA;
/***************** режимы работы ********************/
#define MODE_STOP 0 // останов
#define MODE_AUTO_CHARGE 1 // автоматический заряд
#define MODE_BASE_CHARGE 2 // ручной заряд
#define MODE_DISHARGE 3 // ручной разряд
#define MODE_POWER 4 // источник питания
#define MODE_TRAINING 5 // тренировка - разряд заряд
#define MODE_1CELL 6 // работа с одной ячейкой
#define MODE_NORMAL 7
uint8_t operatingMode = MODE_STOP; // текущий режим
/***************** стадия работы **********************/
#define STOP 0 // стоп
#define ACTIVATION 1 // активация аккумулятора
#define MEASURE_PWM 2 // замер PWM для токов
#define CHARGE_ASSYMETRIC 3 // ассиметричный заряд
#define CHARGE_BASE 4 // основной заряд
#define CHARGE_RECOVERY 5 // заряд восстановления
#define CHARGE_BLEND 6 // перемешивание электролита с добивкой
#define SAVE_CHARGE 7 // хранение
#define DISCHARGE_BASE 8 // разряд
#define DISCHARGE_PULSE 9 // пульсирующий разряд для тестирования
#define DISCHARGE_TEST 10 // разряд для тестирования
#define CHARGE_TEST 11 // заряд для тестирования
#define CHARGE_PULSE1 12 // пульсирующий заряд 1 для тестирования
#define CHARGE_PULSE2 13 // пульсирующий заряд 2 для тестирования
#define ANALYSIS_CONDITION 14 // анализ состояния АКБ
#define ANALYSIS_CHARGE 15 // анализ токов заряда
#define ANALYSIS_DISCHARGE 16 // анализ токов разряда
#define POWER_SUPPLY 17 // источник питания
uint8_t workStage = STOP; // текущая стадия
uint8_t prevWorkStage = STOP; // предыдущая стадия
uint8_t nextWorkStage = STOP; // следующая стадия
/***************** фреймы дисплея **********************/
#define FRAME_INFO 0 // информационный
#define FRAME_VOLTAGE 1 // окно установки напряжений
#define FRAME_CURRENT 2 // окно установки токов
#define FRAME_DURATION 3 // окно установки длительностей
#define FRAME_MODE 11 // окно выбора режима работы
#define FRAME_START_STOP 12 // окно старт/стоп
int8_t curFrame = FRAME_INFO; // текущее окно
uint8_t prevCurFrame;
uint8_t markedLineParam = 1; // активный пункт в окне настроек параметров
uint8_t markedLineMode = 1; // активный пункт в окне выбора режимов работы
/*** Настроечные значения !!! Сотые доли !!! Пример 1220 = 12,20 ***/
int16_t voltActivationMin = 600; // напряжение начала активации 6v
int16_t voltActivationMax = 1220; // напряжение окончания активации
int16_t voltBaseMin = 1400; // напряжение уменьшения тока основного заряда
int16_t voltBaseMax = 1440; // максимальное напряжение для основного заряда
int16_t voltBlendMax = 1560; // максимальное напряжение перемешивания
int16_t voltSaveMin = 1295; // напряжение хранения минимальное
int16_t voltSaveMax = 1370; // напряжение хранения максимальное !!! определить на основном заряде - быстпый рост напряжения
int16_t voltDischargeMin = 1200; // минимальное напряжение для разряда
int16_t ampChargeActivation = 50; // ток активационного заряда
int16_t ampChargeBase = 100; // ток основного заряда
int16_t ampChargeBaseCut = 20; // ток отсечки заряда CV
int16_t ampChargeBlend = 150; // ток заряда перемешивания
int16_t ampChargeSave = 60; // ток заряда при хранении
int16_t ampDischargeMan = 150; // ток разряда
// !!! длительности в милисекундах !!!
uint16_t durActivationCharge = 2000; // длительность заряда активации
uint16_t durActivationPause = 3000; // длительность паузы активации
uint16_t durAssCharge = 3000; // длительность ассиметричного заряда
uint16_t durAssDischarge = 200; // длительность ассиметричного разряда
uint16_t durAssPause = 4000; // длительность ассиметричной паузы
uint8_t nCycles = 1; // число циклов разряд/заряд
uint16_t nImpBlend = 200; // число импульсов перемешивания
/****************** Текущие значения *******************/
uint16_t chargePWM[12]; // значения PWM для токов заряда
uint16_t dischargePWM[12]; // значения PWM для токов разряда
uint8_t capacity; // текущая емкость АКБ, в сотых долях
uint8_t intResistance; // текущее внутреннее сопротивление АКБ
uint8_t cycleCounter; // счетчик циклов зарядов/разрядов
uint16_t voltBattery; // текущее напряжение батареи
int16_t ampCharge; // текущий ток заряда
int16_t ampDischarge; // текущий ток разряда
float chargeEnergy; // полученная энергия при заряде Ам.час
float dischargeEnergy; // отданная энергия при разряде Ам.час
uint16_t pwmCharge = 0; // ШИМ заряда
uint16_t pwmDischarge = 0; // ШИМ разряда
uint32_t onTimeMill; // время от включения прибора, милисекунды
uint32_t onTimeSec = 0; // время от включения прибора, секунды
uint32_t TimeWork = 0; // время работы, секунды
uint16_t timeHours; // часы формат hh:mm:ss
uint16_t timeMins; // минуты формат hh:mm:ss
uint16_t timeSecs; // секунды формат hh:mm:ss
uint8_t nPhase; // текущая фаза в стадии
uint8_t counterPWM; // счетчик замеров PWM токов
uint16_t pulseCounter; // счетчик импульсов в пачке
uint8_t cellDivider=1; // делитель для числа ячеек, для одной банки АКБ = 6
uint32_t dt; // длительность loop для отладки
/****************** Флаги *******************/
bool Correction = false; // идет коррекция тока или напряжения
bool fAllowDisplay = true; // разрешить дисплей
bool fEdit = false; // идет редактирования параметров
bool fAnalysis = false; // идет анализ
bool fAnalysisCompleted = false; // фаза анализа завершена
bool fPause = false; // идет ПАУЗА
bool fPauseCompleted = false; // фаза паузы завершена
bool fCharge = false; // идет заряд
bool fChargeCompleted = true; // фаза заряда завершена
bool fDischarge = false; // идет разряд
bool fDischargeCompleted = true; // фаза разряда завершена
/***************************************************************************/
Adafruit_SSD1306 display( 128, 64, &Wire, -1);
void setup() {
pinMode( pinLeftButton, INPUT);
pinMode( pinRightButton, INPUT);
pinMode( pinOkButton, INPUT);
pinMode( pinDownButton, INPUT);
pinMode( pinUpButton, INPUT);
pinMode( pinLedRed, OUTPUT);
pinMode( pinLedGreen, OUTPUT);
pinMode( pinLedBlue, OUTPUT);
pinMode( pinPwmDischarge, OUTPUT);
pinMode( pinPwmCharge, OUTPUT);
display.begin( SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize( 2);
display.setTextColor( WHITE);
analogPrescaler( 64); // прескалер ADC 16, 32, 64, 128
analogReference(INTERNAL);
Serial.begin(115200);
pwmInit( 10);
workStage = STOP; // workStage = ACTIVATION; для автозапуска
nPhase = 0;
//delay( 3000);
}
void loop()
{
uint8_t charSerial;
/******** для внешнего управления по UART ****/
charSerial = Serial.read();
{
if ( charSerial == 's') // STOP
workStage = STOP;
else if ( charSerial == 'a') // ACTIVATION
workStage = ACTIVATION;
else if ( charSerial == 'c') // CHARGE_BASE
workStage = CHARGE_BASE;
else if ( charSerial == 'd') // DISCHARGE_BASE
workStage = DISCHARGE_BASE;
else if ( charSerial == 's') // SAVE_CHARGE
workStage = SAVE_CHARGE;
else if ( charSerial == 'w') // POWER_SUPPLY
workStage = POWER_SUPPLY;
else if ( charSerial == 't') // CHARGE_TEST
workStage = CHARGE_TEST;
else if ( charSerial == 'z') // DISCHARGE_TEST
workStage = DISCHARGE_TEST;
else if ( charSerial == '.'){ // CHARGE_PULSE1
workStage = CHARGE_PULSE1;
nextWorkStage = CHARGE_PULSE1;
}
else if ( charSerial == ':'){ // CHARGE_PULSE2
workStage = MEASURE_PWM;
nextWorkStage = CHARGE_PULSE2;
}
else if ( charSerial == 'u') // DISCHARGE_PULSE
workStage = DISCHARGE_PULSE;
else if ( charSerial == '!') // установка PWM значений
workStage = MEASURE_PWM;
else if ( charSerial == '@'){ // сброс PWM значений
resetChargePWM();
resetDischargePWM();
}
else if ( charSerial == 'p') { // увеличить ток заряда\разряда
if ( ampChargeBase < 1000) {
ampChargeBase += 10;
ampDischargeMan = ampChargeBase;
}
}
else if ( charSerial == 'm') { // уменьшить ток заряда\разряда
if ( ampChargeBase > 10) {
ampChargeBase -= 10;
ampDischargeMan = ampChargeBase;
}
}
else if ( charSerial == '+') { // увеличить напряжение
if ( voltBaseMax < 1800)
voltBaseMax += 10;
}
else if ( charSerial == '-') { // уменьшить напряжение
if ( voltBaseMax > 10)
voltBaseMax -= 10;
}
}
/*********************************************************************/
getTimeWork(); // заполнение временных отметок
procPresButtons(); // обработка по нажатию кнопок
getVoltageCurrent(); // получить напряжение батареи, токи заряда, разряда
if ( voltBattery < 300 && voltBattery > 60) // если напряжение батареи меньше 3v
operatingMode = MODE_1CELL; // ВОЗМОЖНО!!! подключена одна банка
if ( operatingMode == MODE_1CELL) // режим работы с одной банкой
cellDivider = 6; // установить делитель напряжений
else
cellDivider=1;
/******************************* предохранители ************************/
if ( pwmCharge > 0 && pwmDischarge > 0)
workStage = STOP; // останов по сквозному току
if ( ampCharge > I_MAX || ampDischarge > I_MAX)
workStage = STOP; // останов по превышению токов
if ( workStage != POWER_SUPPLY) // останов если не БП и занижено напряжение АКБ
if ( voltBattery < voltActivationMin/cellDivider)
workStage = STOP;
/************************************************************************/
ledIndication(); // индикация фазы работы RGB светодиодом
calcEnergy(); // подсчет полученной, отданной энергии
if ( fAllowDisplay) // если разрешено
displayFrame( curFrame, markedLineParam); // вывод на дисплей
/***************** отработка стадий и фаз *****************/
switch ( workStage)
{ /************* останов всех стадий работы *************/
case STOP:
ChargingProc( 0, 0, 0, 0, false); // останов заряда
DischargingProc( 0, 0, 0, false); // останов разряда
fPause = false; // сброс флагов
fCharge = false; //
fChargeCompleted = true; //
fDischarge = false; //
fDischargeCompleted = true; //
fAllowDisplay = true; // разрешить дисплей
operatingMode = MODE_STOP; // режим стоп
nPhase = 0; // номер фазы в стадии процесса
nCycles = 0; // счетчик циклов разряд заряд
cellDivider=1; // делитель для числа ячеек
pulseCounter = 0; // счетчик импульсов в пачке
break;
/************* установка PWM токов *************/
case MEASURE_PWM:
switch ( nPhase)
{
case 0:
fAllowDisplay = false; // отключить дисплей
if ( ampCharge > ampChargeBase) { // превышение по току
fAllowDisplay = true;
nPhase ++;
ChargingProc( 0, 0, 0, 0, 0);
break;
}
ChargingProc( ampChargeBase + 10, 0, voltBlendMax/cellDivider, 5000, true);
break;
case 1:
if ( makePause( 2000, 0))
nPhase++;
break;
case 2:
fAllowDisplay = false; // отключить дисплей
if ( ampDischarge > ampChargeBase) { // превышение по току
nPhase ++;
fAllowDisplay = true;
DischargingProc( 0, 0, 0, 0);
break;
}
DischargingProc( ampChargeBase + 10, 100, 10000, true);
break;
case 3:
if ( makePause( 2000, 0)) {
workStage = nextWorkStage; // на следующую стадию
nPhase = 0;
}
break;
}
break;
/************* стадия - предзаряд, активация *************/
case ACTIVATION:
switch ( nPhase) // по фазам
{
case 0:
if ( voltBattery > voltActivationMax/cellDivider) { // предзаряд актуален?
workStage = MEASURE_PWM;
nextWorkStage = CHARGE_ASSYMETRIC; // нет -> заряд
nPhase = 0;
break;
}
if ( ChargingProc( ampChargeActivation, 0, 9999, durActivationCharge, false)) // заряд
nPhase++; // следующая фаза
break;
case 1:
//if ( DischargingProc( 50, 0, 1000, false)) // разряд ???
nPhase++;
break;
case 2: // пауза
if ( makePause( durActivationPause, 0))
nPhase = 0; // сброс номера фазы
break;
}
break;
/************* стадия - ассиметричный заряд или пульсирующий *************/
case CHARGE_ASSYMETRIC:
static uint16_t amp_ch;
switch ( nPhase)
{
case 0: // разряд
if ( durAssDischarge) // если ненулевая длительность
if ( !DischargingProc( amp_ch, 0, durAssDischarge, true))
break; // разряд не закончен
nPhase++;
break;
case 1: // заряд
if ( voltBattery < voltSaveMin/cellDivider)
amp_ch = ampChargeBase;
else if ( voltBattery < voltSaveMax/cellDivider) // снижение тока
amp_ch = ampChargeBase/2;
else if ( voltBattery < voltBaseMin/cellDivider)
amp_ch = ampChargeBase/3;
if ( ChargingProc( amp_ch, 0, voltBaseMax/cellDivider, durAssCharge, true))
nPhase++;
break;
case 2: // пауза
if ( makePause( durAssPause, 0)) {
if ( voltBattery > voltBaseMin/cellDivider)
workStage = CHARGE_BASE; // следующая стадия
}
nPhase = 0;
break;
}
break;
/************* базовый заряд батареи ССCV *************/
case CHARGE_BASE:
if ( operatingMode == MODE_BASE_CHARGE) {
if ( voltBattery < voltSaveMin/cellDivider)
amp_ch = ampChargeBase;
else if ( voltBattery < voltSaveMax/cellDivider) // снижение тока
amp_ch = ampChargeBase/2;
else if ( voltBattery < voltBaseMin/cellDivider)
amp_ch = ampChargeBase/4;
}
if ( ChargingProc( amp_ch, ampChargeBaseCut, voltBaseMax/cellDivider, 0, false)) {
if ( operatingMode == MODE_BASE_CHARGE)
workStage = SAVE_CHARGE;
else
workStage = SAVE_CHARGE; // в последствии на добивку
nPhase = 0;
}
break;
/************* поддержание заряда АКБ *************/
case SAVE_CHARGE:
switch ( nPhase)
{
case 0: // напряжение упало до voltSaveMin
if ( makePause( 0, voltSaveMin/cellDivider))
nPhase++; // на дозаряд
break;
case 1: // дозаряд
if ( ChargingProc( ampChargeBase/4, 0, voltSaveMax/cellDivider, 0, false))
nPhase++;
break;
case 2: // импульс -
fAllowDisplay = false; // запрет вывода на дисплей
if ( ampDischarge > I_MAX - 200 ||
DischargingProc( ampChargeBase/2, voltSaveMin/cellDivider, 300, true)) {
nPhase++;
fAllowDisplay = true;
}
break;
case 3: // импульс +
fAllowDisplay = false; // запрет вывода на дисплей
if ( ampCharge > I_MAX - 200 ||
ChargingProc( ampChargeBase, 0, voltBaseMax/cellDivider, 500, true)) {
nPhase++;
fAllowDisplay = true;
}
break;
case 4: // дозаряд
if ( ChargingProc( ampChargeBase/4, 0, voltSaveMax/cellDivider, 0, true))
nPhase = 0;
break;
}
break;
/************* базовый разряд АКБ *************/
case DISCHARGE_BASE: // разряд батареи до voltDischargeMin
if ( DischargingProc( ampChargeBase, voltDischargeMin/cellDivider-5, 0, false)){
workStage = ACTIVATION; // по окончании заряд
nPhase = 0;
}
break;
/************* пульсирующий разряд АКБ *************/
case DISCHARGE_PULSE:
switch ( nPhase)
{
case 0:
if ( voltBattery < voltDischargeMin/cellDivider-5)
workStage = STOP; // напряжение занижено
nPhase++;
break;
case 1: // разряд
if ( !DischargingProc( ampChargeBase, 0, 3000, true)) // разряд не закончен
break;
nPhase++; // закончен
break;
case 2: // пауза
if ( makePause( 3000, 0))
nPhase = 0;
break;
}
break;
/************* перемешивание ???*************/
case CHARGE_BLEND:
static int16_t delta = 0; // для регулировки тока
switch ( nPhase)
{
case 0: // предзаряд CV c отсечкой по току
if ( ChargingProc( ampChargeBase/3, ampChargeBaseCut, voltBaseMax/cellDivider, 0, false))
nPhase++;
break;
case 1: // импульс перемешивания
if ( pwmCharge == 1023 && ampCharge < ampChargeBlend )
delta += 10; // если падает ток импульса при max ШИМ - уменьшить ток
if ( ChargingProc( ampChargeBlend - delta, ampChargeBlend/2, voltBlendMax/cellDivider, 20000, false))
nPhase++;
break;
case 2: // разряд до voltBaseMin - снятие поляризации, обрыв кипения
if ( DischargingProc( ampChargeBlend, voltBaseMin/cellDivider, 0, true))
nPhase++;
break;
case 3: // короткий импульс 5 амп. десульфатации
fAllowDisplay = false; // запретить дисплей
if ( ChargingProc( 500, 0, voltBlendMax/cellDivider, 100, true)) {
nPhase++;
fAllowDisplay = true;
}
break;
case 4: // пауза до падения напряжения до voltSaveMin
if ( voltBattery <= voltSaveMax/cellDivider) {
nPhase++;
}
break;
case 5: // короткий импульс 5 амп. десульфатации
fAllowDisplay = false; // запретить дисплей
if ( ChargingProc( 500, 0, voltBlendMax/cellDivider, 100, true)) {
nPhase++;
nCycles++;
fAllowDisplay = true;
}
break;
case 6: // пауза до падения напряжения до voltSaveMin
if ( voltBattery <= voltSaveMin/cellDivider) {
delta = 0;
nPhase = 0;
}
break;
}
break;
/************* источник питания *************/
case POWER_SUPPLY:
if ( powerSupply( ampChargeBase, voltBaseMax) ||
pwmCharge >1000 )
workStage = STOP;
break;
/************* тест заряд *************/
case CHARGE_TEST:
fAllowDisplay = false; // отключить дисплей
if ( ampCharge > ampChargeBase || // превышение по току
ChargingProc( ampChargeBase +10, 0, voltBlendMax/cellDivider, 5000, true)) {
workStage = STOP;
fAllowDisplay = true;
nPhase = 0;
}
break;
/************* тест разряд *************/
case DISCHARGE_TEST:
fAllowDisplay = false; // отключить дисплей
if ( ampDischarge > ampChargeBase) { // превышение по току
workStage = STOP;
fAllowDisplay = true;
DischargingProc( 0, 0, 0, 0);
break;
}
DischargingProc( ampChargeBase + 10, voltDischargeMin/cellDivider, 5000, true);
break;
/************* тест пульсирующий заряд *************/
case CHARGE_PULSE1:
switch ( nPhase)
{
case 0:
fAllowDisplay = true;
if ( ChargingProc( ampChargeBase, 0, voltSaveMax/cellDivider, 3000, false)) {
nPhase++;
fAllowDisplay = true;
}
break;
case 1:
if ( makePause( 60000, voltSaveMin/cellDivider))
nPhase = 0;
break;
}
break;
/************* тест пульсирующий заряд *************/
case CHARGE_PULSE2:
switch ( nPhase)
{
case 0:
fAllowDisplay = false; // отключить дисплей
if ( makePause( 20, 0))
nPhase++;
break;
case 1:
if ( ampCharge >ampChargeBase+100) resetChargePWM();
if ( ChargingProc( ampChargeBase, 0, voltBaseMax/cellDivider, 50, true)) { // импульс
nPhase++;
pulseCounter++;
}
break;
case 2:
if ( ampDischarge >ampChargeBase+100) resetDischargePWM();
if ( DischargingProc( ampChargeBase/2, voltDischargeMin/cellDivider, 50, true)) { // импульс разряда
nPhase++;
pulseCounter++;
}
break;
case 3:
if ( pulseCounter > 20) { // после 20 импульсов
fAllowDisplay = true; // включить дисплей
if ( ChargingProc( ampChargeBase, ampChargeBase/3,
voltSaveMax/cellDivider, 20000, true)) { // заряд
pulseCounter = 0; // обнулить счетчик
nPhase++; // на паузу
}
} else nPhase = 0; // продолжить импульсы
break;
case 4:
if ( DischargingProc( ampChargeBase/2, voltDischargeMin/cellDivider, 100, true)) // разряд
nPhase++;
break;
case 5:
if ( makePause( 2000, 0))
nPhase++;
break;
case 6:
if ( makePause( 100000, voltSaveMin/cellDivider))
nPhase= 0;
break;
}
break;
}
/**************************************************************/
if ( prevWorkStage != workStage)
prevWorkStage = workStage; // сохранение предыдущей фазы работы
dt = millis() - dt; // замер длительность loop
// if ( dt > 5)
{ // вывод в сериал для лога и отладки
// Serial << F("dT ") << dt << '\n';
if ( voltBattery > 300)
Serial <<voltBattery<<' '<<ampCharge/10 + 1200<<' '<<1200+ampDischarge/10<<' '<<1200+ampChargeBase/10<<' '<<pwmCharge/10+1200<< ' '<<1200-pwmDischarge/10<<' '<<dt<<'\n';
else
Serial <<voltBattery<<' '<<ampCharge<<' '<<ampDischarge<<' '<<ampChargeBase<<'\n';
}
dt = millis();
}
/*****************************************************
* Подсчет полученной, отданной энергии в ампер часах
* calcEnergy() каждую секунду
*****************************************************/
void calcEnergy(){
static uint32_t prevTimeCalcEnergy = 0;
if ( onTimeSec > prevTimeCalcEnergy) {
prevTimeCalcEnergy = onTimeSec;
chargeEnergy += ((float)ampCharge) / 360000.0;
dischargeEnergy += ((float)ampDischarge) / 360000.0;
}
}
/*****************************************************
* сделать паузу по времени или падению до напряжения
* bool makeРause(uint32_t tpause, int16_t voltage)
* tpause - время паузы в милисекундах
* voltage - по падению до напряжения, приоритетно
* return false - пауза не окончена
* true - пауза закончилась
****************************************************/
bool makePause( uint32_t tpause, int16_t voltage)
{
static uint32_t tpStart = 0;
fPauseCompleted = false;
if ( voltBattery >= voltage){ // если напряжение не упало ниже voltage
if ( tpause) { // если установлена длительность
if ( !tpStart) tpStart = millis();
if (( millis() - tpStart) >= tpause) { // если вышло время
tpStart = 0;
fPause = false;
fPauseCompleted = true;
return true; // пауза окончена
} else {
fPause = true;
return false;
}
}
fPause = true;
return false;
} else {
tpStart = 0;
fPause = false;
fPauseCompleted = true;
return true;
}
}
/*************************************************************************************
* Заряд аккумулятора СС - постоянным током до достижения voltage на аккумуляторе.
* Если cutcurrent не равен 0, то после достижения voltage, заряд CV - падающим током до cutcurrent
* В любом случае, если установлена длительность заряда, окончание по прывышению длительности
*
* bool ChargingProc( uint16_t current, uint16_t cutcurrent, uint16_t maxvoltage, uint32_t duration)
* current - ток заряда, если 0 - прекращение заряда
* cutcurrent - min ток прекращения заряда, если 0 - не учитывается, отсечка по voltage
* voltage - предел заряда по напряжению
* duration - макс длительность заряда в милисекундах, если 0 - не учитывается
* pulse - если true, резкий фронт нарастания, pwmCharge ,берется из chargePWM[]
* return false - заряд не окончен
* true - заряда закончен
* устанавливает глоб. флаги
* fChargeCompleted = true - завершение по напряжению, длительности или по отсечке тока
* fCharge = true - заряд идет
* глобальные значения
* voltBattery, ampCharge
* примеры !!! следить за сочетанием параметров
* ChargingProc( 100, 50, 1460, 0, false)
* ChargingProc( 100, 0, 1460, 0, true)
* ChargingProc( 100, 0, 1460, 2000, true)
* ChargingProc( 100, 50, 1460, 2000, false)
****************************************************/
bool ChargingProc( uint16_t current, uint16_t cutcurrent, uint16_t maxvoltage, uint32_t duration, bool pulse)
{
uint16_t dPwm; // дельта приращения pwm
int16_t temp_cur; // временная переменная
static bool fCV = false; // флаг CV - поддержания напряжения, до cutcurrent
static bool fStartPulse = true; // начало импульса
static uint32_t start_time = 0; // время начала фазы заряда
static uint32_t charge_duration = 0; // прошло времени
fChargeCompleted = false;
fStartPulse ;
if ( duration){
if ( !start_time)
start_time = onTimeMill; // установка времени начала фазы заряда
charge_duration = duration + start_time - onTimeMill;
}
/* завершение если .. */
if ( current == 0 || fPause || fDischarge ||
charge_duration > duration ||
( voltBattery > maxvoltage && cutcurrent == 0 )) {
L_EndCharge: // окончание заряда
fChargeCompleted = true;
start_time = 0;
charge_duration = 0;
pwmCharge = 0;
fCV = false;
digitalWrite10( pwmCharge); // ШИМ = 0
fCharge = false;
fStartPulse = true;
return true;
}
/******** вычисление приращения ШИМ ********/
if ( ampCharge <= current+current/5 && ampCharge >= current-current/5)
dPwm = 1;
else if ( pwmCharge < 200) dPwm = 5;
else if ( pwmCharge < 500) dPwm = 2;
else dPwm = 1;
/*********************************************/
if ( voltBattery >= maxvoltage) // напряжение достигнуто
{
fCV = true; // в режим CV
if ( ampCharge > cutcurrent) { // ток отсечки не достигнут
if ( pwmCharge > dPwm) {
pwmCharge -= dPwm; // уменьшение тока заряда
goto L_ContCharge; // продолжить заряда
}
} else
goto L_EndCharge; // закончить заряд
}
/******************* заполнение массива chargePWM *********************/
temp_cur = (ampCharge+10)/100*100;
if ( ampCharge > 0 && ampCharge < 50) // для малых токов
chargePWM[0] = pwmCharge;
else if ( temp_cur && ampCharge > temp_cur-20 && ampCharge < temp_cur + 10)
chargePWM[temp_cur/100] = pwmCharge;
/********************* если импульс и начало его *********************/
if ( pulse && fStartPulse) {
if ( current < 1200) {
temp_cur = current/100; // индекс массива PWM
while ( temp_cur >= 0) { //
pwmCharge = chargePWM[temp_cur]; //
if ( !pwmCharge ) // если нет значения в массиве по текущему индексу
temp_cur--; // берется предыдущий
else
break; // выход из цикла если найден
}
}
fStartPulse = false; //
}
if ( voltBattery < maxvoltage && fCV) // в режиме CV
pwmCharge += dPwm;
if ( !fCV) { // в режиме CС
if ( ampCharge < current) {
if ( pwmCharge < 1023) pwmCharge += dPwm;
}
else
{
if ( ampCharge > current)
if ( pwmCharge > dPwm) pwmCharge -= dPwm;
}
}
L_ContCharge:
fCharge = true;
//fStartPulse = false;
pwmWrite10( pwmCharge); // установка ШИМ
return false;
}
/*****************************************************
* как источник питания
* bool powerSupply( uint16_t current, uint16_t voltage)
* current - ток, voltage - напряжение
******************************************************/
bool powerSupply( uint16_t current, uint16_t voltage) {
if ( ampCharge > 600 || !current || !voltage) { // превышение по току или неверные параметры
pwmCharge = 0;
pwmWrite10( pwmCharge);
digitalWrite10( pwmCharge); // ШИМ = 0
fChargeCompleted = true;
fCharge = false;
return true; // завершено
}
if ( ampCharge > current || voltBattery > voltage)
pwmCharge--;
else
pwmCharge++;
fChargeCompleted = false;
fCharge = true;
pwmWrite10( pwmCharge);
return false;
}
/*****************************************************
* Разряд аккумулятора постоянным током current
* bool DischargingProc( uint16_t current, uint16_t voltage, uint32_t duration, bool pulse)
* current - ток разряда Амперы
* voltage - предел разряда по напряжению
* duration - макс длительность разряда в милисекундах, если 0 - не учитывается
* pulse - если true, резкий фронт нарастания, pwmDischarge ,берется из dischargePWM[]
* return
* false - разряд продолжается
* true - разряд по условию закончен
* устанавливает глоб. флаги
* fDischargeCompleted = true завершение по напряжению
* fDischarge = true - разряд идет
* примеры
* DischargingProc( 100, 1200, 2000) завершение по напряжению и длительности
* DischargingProc( 100, 0, 2000) завершение по длительности
* DischargingProc( 100, 1200, 0) завершение по напряжению
*****************************************************/
bool DischargingProc( uint16_t current, uint16_t voltage, uint32_t duration, bool pulse)
{
uint16_t dPwm; // дельта приращения pwm
int16_t temp_cur; // временная переменная
static uint32_t start_time = 0; // время начала фазы разряда
static uint32_t dischg_duration = 0; // прошло времени в разряде
static bool fStartPulse = true; // начало импульса
fDischargeCompleted = false;
if ( duration){ // если указана длительность
if ( !start_time)
start_time = onTimeMill; // установка времени начала фазы заряда
dischg_duration = duration + start_time - onTimeMill;
}
/* завершение если .. -> останов разряда */
if ( current == 0 || voltBattery < voltage || dischg_duration > duration || fPause || fCharge) {
start_time = 0;
dischg_duration = 0;
pwmDischarge = 0;
digitalWrite9( pwmDischarge); // ШИМ = 0
fDischargeCompleted = true;
fDischarge = false;
fStartPulse = true;
return true;
}
/************ вычисление приращения ШИМ *********/
/* if ( !fDischarge)
pwmDischarge = 760; // !!! смещение для открытия MOSFET разряда
if ( ampDischarge <= current+current/10 && ampCharge >= current-current/10)
dPwm = 1;
else if ( pwmDischarge < 200) dPwm = 30;
else if ( pwmDischarge < 500) dPwm = 20;
else if ( pwmDischarge < 700) dPwm = 2;
else dPwm = 1; */
dPwm = 1;
/****************** регулировка ******************/
if ( ampDischarge < current) {
if ( pwmDischarge < 1023) pwmDischarge += dPwm;
} else {
if ( ampDischarge > (current + 2))
if ( pwmDischarge > dPwm) pwmDischarge -= dPwm;
}
/******* заполнение массива dischargePWM[] ******/
if ( ampDischarge > 30 && ampDischarge < 60) // для малых токов
dischargePWM[0] = pwmDischarge;
temp_cur = (ampDischarge+10)/100*100;
if ( temp_cur && ampDischarge > temp_cur-10 && ampDischarge < temp_cur + 10)
dischargePWM[temp_cur/100] = pwmDischarge;
/********** если импульс и начало его ************/
if ( pulse && fStartPulse) {
if ( current < 1200) {
temp_cur = current/100;
while ( temp_cur >= 0) {
pwmDischarge = dischargePWM[temp_cur];
if ( !pwmDischarge )
temp_cur--;
else
break;
}
}
fStartPulse = false;
}
/******************************************/
fDischarge = true;
pwmWrite9( pwmDischarge);
return false;
}
/*****************************************************
* getVoltageCurrent() - считать текущие напряжение и токи
* запись в глобальные переменные voltBattery ampCharge ampDischarg
*****************************************************/
void getVoltageCurrent(){
uint16_t buf;
uint32_t chg_cur, dischg_cur;
{ // замер напряжение
buf = 0;
for (int i=0; i <= 24; i++) {
buf += analogRead( pinADC_Voltage);
}
buf = buf / 24;
voltBattery = ((uint32_t)buf) * 25 /10;
}
{ // замер тока заряда
buf = 0;
for (int i=0; i <= 16; i++) {
buf += analogRead( pinADC_ChargeCurrent);
}
buf = buf / 16;
ampCharge = buf * 3; // шунт - 3 резистора 0.1 в параллель
if ( ampCharge)
ampCharge += 5;
}
{ // ток разряда
buf = 0;
for (int i=0; i <= 16; i++) {
buf += analogRead( pinADC_DischargeCurrent);
}
buf = buf / 16;
ampDischarge = buf * 2; // шунт - 2 резистора 0.1 в параллель
if ( ampDischarge > 100)
ampDischarge = ampDischarge*9/10 + 10;
else if ( ampDischarge > 0 && ampDischarge < 100)
ampDischarge = ampDischarge*94/100 + 5;
}
if ( voltBattery > 50 && voltBattery < 2000) voltBattery += (2000-voltBattery)/220; // поправка для низких напряжений
voltBattery -= -ampDischarge/10 + ampCharge/16; // поправка падения U на шунтах и проводах
}
/*****************************************************
* getTimeWork() - заполнение временных меток
*****************************************************/
void getTimeWork()
{
static bool fFirstStart = true; // флаг начала сеанса
static uint32_t timeStart = 0; // время старта сеанса работы
static uint32_t timePrev = 0; // длительность предыдущего сеанса
onTimeMill = millis(); // время от включения ЗУ в милисекундах
onTimeSec = onTimeMill / 1000ul; // время от включения ЗУ в секундах
if ( (workStage != STOP) && fFirstStart) { // если не STOP и первый запуск в сеансе
fFirstStart = false; // отметка
timeStart = onTimeMill;
}
if ( workStage != STOP) { // если не в стопе
TimeWork = (onTimeMill - timeStart) / 1000ul + timePrev; // секунд в работе
} else {
if ( prevWorkStage != STOP) // если пред не стоп сохр. длит.
timePrev = TimeWork;
timeStart = 0;
fFirstStart = true;
}
timeHours = (TimeWork / 3600ul); // часов в работе
timeMins = (TimeWork % 3600ul) / 60ul; // минут
timeSecs = (TimeWork % 3600ul) % 60ul; // секунд
}
/*****************************************************
* displayFrame( frame, marked) вывод на дисплей
* frame - номер фрейма
* marked - помеченная строка, 0 - без метки
*****************************************************/
void displayFrame( uint8_t frame, uint8_t marked)
{
static unsigned long tDisplay; // время вывода на дисплей
char bufStr[12]; // буфер формирования строки вывода
unsigned long ttt = millis();
if ( (ttt - tDisplay) >= 263) // период обновление экрана
tDisplay = ttt; // обновить
else
return;
display.setTextSize( 2);
display.clearDisplay();
display.setCursor(0,0);
if ( frame == FRAME_INFO) { // основной рабочий-информационный экран
uint16_t voltInfo; //
int16_t current = ( ampCharge > ampDischarge ? ampCharge : -ampDischarge);
// uint16_t voltage = voltBattery;
if ( workStage == DISCHARGE_BASE)
voltInfo = voltDischargeMin/cellDivider;
else if ( workStage == CHARGE_BLEND)
voltInfo = voltBlendMax/cellDivider;
else
voltInfo = voltBaseMax; // /cellDivider;
/* округление до десятых */
// if ( current > 10 )
// current % 10 > 4 ? current = current/10*10+10 : current = current/10*10;
// else if ( current < -10 )
// current % 10 < -4 ? current = current/10*10-10 : current = current/10*10;
//if ( voltage > 10 )
// voltage % 10 > 4 ? voltage = voltage/10*10+10 : voltage = voltage/10*10;
sprintf(bufStr, "%2d.%02dv%2d.%1d", voltBattery/100, (voltBattery%100), voltInfo/100, (voltInfo%100)/10 );
display.print(bufStr);
display.setCursor( 0, 16);
sprintf(bufStr, "%2d.%02da%2d.%1d", current/100, (abs(current)%100), ampChargeBase/100, (abs(ampChargeBase)%100)/10);
display.println(bufStr);
display.setCursor( 0, 32);
uint32_t dt = ( onTimeSec + 1)%5;
if ( dt == 1 || dt == 2 ) { // отображение 2 секунды
if ( operatingMode == MODE_1CELL) display.print ( F("CELL "));
if ( workStage == STOP) display.println( F("STOP"));
else if ( workStage == ACTIVATION) display.print ( F("ACTIV"));
else if ( workStage == CHARGE_ASSYMETRIC) display.print ( F("ASMCH"));
else if ( workStage == CHARGE_BASE || workStage == CHARGE_TEST)
display.println( F("CHARG"));
else if ( workStage == CHARGE_BLEND) display.print ( F("BLEND"));
else if ( workStage == SAVE_CHARGE ) display.println( F("SAVE"));
else if ( workStage == POWER_SUPPLY) display.print ( F("POWER"));
else if ( workStage == CHARGE_PULSE1 || workStage == CHARGE_PULSE2)
display.print ( F("PULSE"));
else if ( workStage == DISCHARGE_BASE || workStage == DISCHARGE_TEST) {
display.println( F("DISCH"));
display.print ( cycleCounter+1);
}
sprintf(bufStr, "%3d:%02d:%02d", timeHours, timeMins, timeSecs);
display.setCursor( 0, 48);
display.print(bufStr);
}
else if ( dt == 3 || dt == 4) { // полученная отданная емкость
sprintf(bufStr, "+ %d.%02dah", (int)(chargeEnergy),(int)(chargeEnergy*100)%100);
display.println(bufStr);
sprintf(bufStr, "- %d.%02dah", (int)(dischargeEnergy),(int)(dischargeEnergy*100)%100);
display.println(bufStr);
sprintf(bufStr, "Pwm %04d", pwmCharge > pwmDischarge ? pwmCharge : -pwmDischarge);
display.println(bufStr);
} else { //
display.setCursor( 0, 48);
sprintf(bufStr, "Pwm %04d", pwmCharge > pwmDischarge ? pwmCharge : -pwmDischarge);
display.println(bufStr);
}
}
else if ( frame == FRAME_START_STOP) { // экран запроса СТАРТ/СТОП
sprintf(bufStr, "Vb %02d.%02dv", voltBattery/100, voltBattery%100);
display.println(bufStr);
display.setTextSize( 3);
display.setCursor(0,24);
if ( workStage != STOP) display.println( F(" STOP?"));
else display.println( F(" START?"));
}
else if ( frame == FRAME_MODE) { // экран запроса режима работы
if ( markedLineMode < 5) {
display.println( F(" Auto"));
display.println( F(" Charge "));
display.print ( F(" DisCharge"));
display.print ( F(" Power"));
display.setCursor( 0, ( markedLineMode -1 ) *16);
} else if ( markedLineMode < 9) {
display.println( F(" Pulse1"));
display.println( F(" Pulse2"));
display.println( F(" ..."));
display.print ( F(" ..."));
display.setCursor( 0, ( markedLineMode -5 ) *16);
}
display.print( F("> "));
}
else if ( frame == FRAME_VOLTAGE) { // экран установок напряжений - 6 параметров
display.setTextSize( 1);
display.print( F(" Uact_min ")); display.println( (float)voltActivationMin / 100.0);
display.print( F(" Uact_max ")); display.println( (float)voltActivationMax / 100.0);
display.print( F(" Ubase_min ")); display.println( (float)voltBaseMin / 100.0);
display.print( F(" Ubase_max ")); display.println( (float)voltBaseMax / 100.0);
display.print( F(" Urepair ")); display.println( (float)voltBlendMax / 100.0);
display.print( F(" Usave ")); display.println( (float)voltSaveMin / 100.0);
display.print( F(" Urazr_min ")); display.println( (float)voltDischargeMin / 100.0);
display.println( F(" ..."));
display.setCursor( 0, ( marked -1 ) *8);
if ( fEdit) display.print( F("* "));
else display.print( F("> "));
}
else if ( frame == FRAME_CURRENT) { // экран установок токов - 8 параметров
display.setTextSize( 1);
display.print( F(" Iactivat ")); display.println( (float)ampChargeActivation / 100.0);
display.print( F(" Ichg_base ")); display.println( (float)ampChargeBase / 100.0);
display.print( F(" Icut_base ")); display.println( (float)ampChargeBaseCut / 100.0);
display.print( F(" Irepair ")); display.println( (float)ampChargeBlend / 100.0);
display.print( F(" Isave ")); display.println( (float)ampChargeSave / 100.0);
display.print( F(" Irazryad ")); display.println( (float)ampDischargeMan / 100.0);
display.println( F(" ..."));
display.print( F(" ..."));
display.setCursor( 0, ( marked -1 ) *8);
if ( fEdit) display.print( F("* "));
else display.print( F("> "));
}
else if ( frame == FRAME_DURATION) { // экран установок длительностей - 8 параметров
display.setTextSize( 1);
display.print( F(" Tact_charg ")); display.println( (float)durActivationCharge / 1000.0);
display.print( F(" Tact_pause ")); display.println( (float)durActivationPause / 1000.0);
display.print( F(" Tass_charg ")); display.println( (float)durAssCharge / 1000.0);
display.print( F(" Tass_dischg ")); display.println( (float)durAssDischarge / 1000.0);
display.print( F(" Tass_pause ")); display.println( (float)durAssPause / 1000.0);
display.println( F(" ..."));
display.println( F(" ..."));
display.print( F(" ..."));
display.setCursor( 0, ( marked -1 ) *8);
if ( fEdit) display.print( F("* "));
else display.print( F("> "));
}
display.display();
}
/****************************************************
* uint8_t getButtonState() опрос кнопок
* возврат кода нажатой кнопки - порт D
* при долгом нажатии - повтор
*****************************************************/
uint8_t getButtonState()
{
static uint32_t pressTime = 0; // время нажатия кнопки
static uint8_t button; // код кнопки
static bool fSend0 = false; // отправлено в четную полусекунду
static bool fSend1 = false; // отправлено в нечетную полусекунду
uint8_t state = (~PIND & 0b1111100) >> 2; // на порту D, состояние, код кнопки
if ( state && !pressTime) // начало нажатия
{
pressTime = onTimeMill; // время начала нажатия
button = state; // код кнопки
}
if ( !state && pressTime) // если кнопка отпущена
{
if ( onTimeMill - pressTime > 80)
{
// Serial << onTimeMill - pressTime<< '\n';
pressTime = 0;
fSend0 = false;
fSend1 = false;
if ( onTimeMill - pressTime < 1500)
return 0;
return button;
}
}
if ( state && pressTime && ( onTimeMill - pressTime) > 1000) { // длительное нажатие - повтор
if ( (onTimeMill/200+1)%2) { // нечетная доля
fSend0 = false;
if ( !fSend1) { // и не отправлено
fSend1 = true;
return button; // возврат кода кнопки
} else
return 0;
} else { // четная доля
fSend1 = false;
if ( !fSend0) { // и не отправлено
fSend0 = true;
return button; // возврат кода кнопки
} else return 0; //
}
}
return 0;
}
/*****************************************************
* Обработка по нажатию кнопок
* void procPresButtons()
*****************************************************/
void procPresButtons()
{
uint8_t button = getButtonState();
switch ( button)
{
case BUT_OK:
if ( curFrame == FRAME_INFO) { // переход на выбор режима работы с последующим стартом или останов
if ( workStage != STOP) curFrame = FRAME_START_STOP; // если в работе в окно старт\стоп
else curFrame = FRAME_MODE; // если в останове - > выбор режима работы
}
else if ( curFrame == FRAME_START_STOP) { // если в окне старт\стоп
if ( workStage != STOP) // и в работе
{
workStage = STOP; // в останов
nPhase = 0;
} else {
cycleCounter = 0; // по старту сброс счетчика тренировок
workStage = MEASURE_PWM;
if ( markedLineMode == 1) { // если MODE_AUTO_CHARGE
operatingMode = MODE_AUTO_CHARGE;
workStage = ACTIVATION;
}
if ( markedLineMode == 2) { // MODE_BASE_CHARGE
operatingMode = MODE_BASE_CHARGE;
nextWorkStage = CHARGE_BASE;
}
if ( markedLineMode == 3) { // MODE_DISHARGE
operatingMode = MODE_DISHARGE;
workStage = DISCHARGE_BASE;
}
if ( markedLineMode == 4) { // MODE_POWER
operatingMode = MODE_POWER;
workStage = POWER_SUPPLY;
}
if ( markedLineMode == 5) { // CHARGE_PULSE1
operatingMode = MODE_NORMAL;
nextWorkStage = CHARGE_PULSE1;
}
if ( markedLineMode == 6) { // CHARGE_PULSE2
operatingMode = MODE_NORMAL;
nextWorkStage = CHARGE_PULSE2;
}
resetChargePWM();
resetDischargePWM();
nPhase = 0;
}
curFrame = FRAME_INFO;
}
else if ( curFrame == FRAME_MODE)
curFrame = FRAME_START_STOP;
else if ( curFrame == FRAME_VOLTAGE || curFrame == FRAME_CURRENT || curFrame == FRAME_DURATION)
fEdit = !fEdit; // установить флаг правки параметров
break;
case BUT_RIGHT: // переход между окнами и коррекция Umax
case BUT_LEFT:
if ( workStage == STOP && !fEdit) {
markedLineParam = 1;
if ( curFrame == FRAME_START_STOP || curFrame == FRAME_MODE)
curFrame = FRAME_INFO;
else if ( button == BUT_RIGHT) curFrame++;
else curFrame--;
if ( curFrame < FRAME_INFO) curFrame = FRAME_DURATION;
if ( curFrame > FRAME_DURATION) curFrame = FRAME_INFO;
}
else if ( curFrame == FRAME_START_STOP) curFrame = FRAME_INFO; //
else if ( workStage == CHARGE_BLEND) {
button == BUT_RIGHT ? voltBlendMax += 10 : voltBlendMax -= 10;
if ( voltBlendMax < 1470) voltBlendMax = 1470;
else if ( voltBlendMax > 1670) voltBlendMax = 1670;
} else if ( workStage == DISCHARGE_BASE || workStage == DISCHARGE_PULSE){
button == BUT_RIGHT ? voltDischargeMin += 10 : voltDischargeMin -= 10;
if ( voltDischargeMin < 1050) voltDischargeMin = 1050;
else if ( voltDischargeMin > 1280) voltDischargeMin = 1280;
} else if ( workStage != STOP){
button == BUT_RIGHT ? voltBaseMax += 10 : voltBaseMax -= 10;
if ( voltBaseMax < 200) voltBaseMax = 200;
else if ( voltBaseMax > 1700) voltBaseMax = 1700;
}
break;
case BUT_UP: // если не редактирование движение по пунктам окна
case BUT_DOWN: // вверх вниз
if ( fEdit) { // если редактирование - ручная правка базовых параметров
if ( curFrame == FRAME_VOLTAGE){ // если окно настройки напряжений
if ( markedLineParam == 1){ // напряжение активации минимальное
button == BUT_UP ? voltActivationMin += 10 : voltActivationMin -= 10;
if ( voltActivationMin < 600) voltActivationMin = 600;
else if ( voltActivationMin > 1200) voltActivationMin = 1200;
}
if ( markedLineParam == 2){ // напряжение окнчания активации
button == BUT_UP ? voltActivationMax += 10 : voltActivationMax -= 10;
if ( voltActivationMax < 1000) voltActivationMax = 1000;
else if ( voltActivationMax > 1240) voltActivationMax = 1240;
}
if ( markedLineParam == 3){ // напряжение снижения тока для base заряда
button == BUT_UP ? voltBaseMin += 10 : voltBaseMin -= 10;
if ( voltBaseMin < 1300) voltBaseMin = 1300;
else if ( voltBaseMin > 1420) voltBaseMin = 1420;
}
if ( markedLineParam == 4){ // максимальное напряжение для base заряда
button == BUT_UP ? voltBaseMax += 10 : voltBaseMax -= 10;
if ( voltBaseMax < 1380) voltBaseMax = 1380;
else if ( voltBaseMax > 1470) voltBaseMax = 1470;
}
if ( markedLineParam == 5){ // максимальное напряжение перемешивания
button == BUT_UP ? voltBlendMax += 10 : voltBlendMax -= 10;
if ( voltBlendMax < 1470) voltBlendMax = 1470;
else if ( voltBlendMax > 1670) voltBlendMax = 1670;
}
if ( markedLineParam == 6){ // напряжение хранения минимальное
button == BUT_UP ? voltSaveMin += 10 : voltSaveMin -= 10;
if ( voltSaveMin < 1280) voltSaveMin = 1280;
else if ( voltSaveMin > 1340) voltSaveMin = 1340;
}
if ( markedLineParam == 7){ // минимальное напряжение для разряда
button == BUT_UP ? voltDischargeMin += 10 : voltDischargeMin -= 10;
if ( voltDischargeMin < 1050) voltDischargeMin = 1050;
else if ( voltDischargeMin > 1280) voltDischargeMin = 1280;
}
}
if ( curFrame == FRAME_CURRENT){ // окно настройки токов
if ( markedLineParam == 1){ // ток заряда активации
button == BUT_UP ? ampChargeActivation += 10 : ampChargeActivation -= 10;
if ( ampChargeActivation < 20) ampChargeActivation = 20;
else if ( ampChargeActivation > 500) ampChargeActivation = 500;
}
if ( markedLineParam == 2){ // ток основного заряда
button == BUT_UP ? ampChargeBase += 10 : ampChargeBase -= 10;
if ( ampChargeBase < 10) ampChargeBase = 10;
else if ( ampChargeBase > 900) ampChargeBase = 900;
}
if ( markedLineParam == 3){ // ток отсечки заряда CV
button == BUT_UP ? ampChargeBaseCut += 10 : ampChargeBaseCut -= 10;
if ( ampChargeBaseCut < 10) ampChargeBaseCut = 10;
else if ( ampChargeBaseCut > 200) ampChargeBaseCut = 200;
}
if ( markedLineParam == 4){ // ток заряда REPAIR
button == BUT_UP ? ampChargeBlend += 10 : ampChargeBlend -= 10;
if ( ampChargeBlend < 10) ampChargeBlend = 10;
else if ( ampChargeBlend > 800) ampChargeBlend = 800;
}
if ( markedLineParam == 5){ // ток хранения
button == BUT_UP ? ampChargeSave += 10 : ampChargeSave -= 10;
if ( ampChargeSave < 10) ampChargeSave = 10;
else if ( ampChargeSave > 200) ampChargeSave = 200;
}
if ( markedLineParam == 6){ // ток разряда
button == BUT_UP ? ampDischargeMan += 10 : ampDischargeMan -= 10;
if ( ampDischargeMan < 10) ampDischargeMan = 10;
else if ( ampDischargeMan > 900) ampDischargeMan = 900;
}
if ( markedLineParam == 7){ // число циклов тренировки
button == BUT_UP ? nCycles++ : nCycles--;
if ( nCycles < 1) nCycles = 1;
else if ( nCycles > 10) nCycles = 10;
}
}
if ( curFrame == FRAME_DURATION){ // окно длительностей
if ( markedLineParam == 1){ // длительность фазы заряда при активации
button == BUT_UP ? durActivationCharge += 100 : durActivationCharge -= 100;
if ( durActivationCharge < 100) durActivationCharge = 100;
else if ( durActivationCharge > 60000) durActivationCharge = 60000;
}
if ( markedLineParam == 2){ // длительность паузы при активации
button == BUT_UP ? durActivationPause += 100 : durActivationPause -= 100;
if ( durActivationPause < 100) durActivationPause = 100;
else if ( durActivationPause > 60000) durActivationPause = 60000;
}
if ( markedLineParam == 3){ // длительность фазы заряда в ассиметричном заряде
button == BUT_UP ? durAssCharge += 100 : durAssCharge -= 100;
if ( durAssCharge < 100) durAssCharge = 100;
else if ( durAssCharge > 60000) durAssCharge = 60000;
}
if ( markedLineParam == 4){ // длительность фазы разряда в ассиметричном заряде
button == BUT_UP ? durAssDischarge += 100 : durAssDischarge -= 100;
if ( durAssDischarge < 100) durAssDischarge = 0;
else if ( durAssDischarge > 60000) durAssDischarge = 60000;
}
if ( markedLineParam == 5){ // длительность паузы в ассиметричном заряде
button == BUT_UP ? durAssPause += 100 : durAssPause -= 100;
if ( durAssPause < 100) durAssPause = 100;
else if ( durAssPause > 60000) durAssPause = 60000;
}
}
}
if ( curFrame == FRAME_INFO) { // изменение основного тока
button == BUT_UP ? ampChargeBase += 10 : ampChargeBase -= 10;
if ( ampChargeBase < 10) ampChargeBase = 10;
else if ( ampChargeBase > 700) ampChargeBase = 700;
break;
}
// 8 пунктов в окне настроек
if ( curFrame > FRAME_INFO && curFrame < FRAME_MODE && !fEdit) {
button == BUT_UP ? markedLineParam-- : markedLineParam++;
if ( !markedLineParam) markedLineParam = 8;
else if ( markedLineParam > 8) markedLineParam = 1;
} // 8 пунктов в окне выбора режимов работы
else if ( curFrame == FRAME_MODE) {
button == BUT_UP ? markedLineMode-- : markedLineMode++;
if ( !markedLineMode) markedLineMode = 8;
else if ( markedLineMode > 8) markedLineMode = 1;
}
break;
}
}
/*****************************************************
* Индиация фазы работы RGB светодиодом
* инверсия сигналов
*****************************************************/
void ledIndication()
{
uint8_t dt = ( onTimeSec + 1)%2;
switch ( workStage)
{
case STOP: // останов - белый
case ANALYSIS_CONDITION: //
case ANALYSIS_CHARGE:
case ANALYSIS_DISCHARGE:
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 0);
break;
case ACTIVATION: // активация - желтый мигающий
if ( dt ) {
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
} else {
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
}
break;
case CHARGE_PULSE1:
case CHARGE_PULSE2: // импульсы заряда - красный - желтый
if ( dt ) {
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
} else {
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
}
break;
case CHARGE_ASSYMETRIC: // ассиметричный заряд - красный синий
if ( dt ) {
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
} else {
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 0);
}
break;
case CHARGE_TEST: // основной заряд - красный
case CHARGE_BASE:
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
break;
case CHARGE_BLEND: // восстановление - зеленый мигающий
if ( dt ) {
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
} else {
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 1);
}
break;
case SAVE_CHARGE: // хранение - зеленый
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
break;
case DISCHARGE_BASE: // разряд - синий
case DISCHARGE_TEST:
digitalWrite(pinLedRed, 1);
digitalWrite(pinLedGreen, 1);
digitalWrite(pinLedBlue, 0);
break;
case POWER_SUPPLY: // БП - желтый
digitalWrite(pinLedRed, 0);
digitalWrite(pinLedGreen, 0);
digitalWrite(pinLedBlue, 1);
break;
}
}
/*****************************************************
* Сброс массива значений PWM для токов
*****************************************************/
void resetChargePWM( void){
for ( uint8_t i=1; i<12; i++) {
chargePWM[i] = 0;
}
return;
}
void resetDischargePWM( void){
for ( uint8_t i=1; i<12; i++) {
chargePWM[i] = 0;
}
return;
}
Не хочу подрывать ваш энтузиазм - но вот на моей памяти - еще с тех лет когда я школьником читал свой любимый журнал "Радио" - множество людей пытались добиться "ясности" в этом неблагодарном деле "восстановления автомобильных АКБ".. Всех благ - и успеха !
Нонсенс, но в большинстве случаев дорога к цели или постижение "ясности", гораздо более интересны, чем обладание окончательным результатом. ;)
Такова наверно, суть творческого потенциала человека. Если он имеется конечно и есть возможность его реализации.
А в остальном, ситуация по алгоритмам пока в стадии - "чем дальше в лес, тем толще партизаны" :D
Добрый день, уважаемый Домосед.
Подскажите:
1.как можно правильно подсоединить энкодер вместо кнопок? (нет места в коробочке)
2.я правильно понял, что последние: схема 05/08/2022 - 22:48, скетчи 10/08/2022 - 23:24?
Доброго дня! К сожалению в своих поделках до текущего момента обходился без энкодеров. Наверно потому, что у меня кнопок этих, ну просто завались :D И к сожалению пока не планирую отходить от этой традиции. Тем более, что это потребует существенной переделки кода, на что просто нет времени. Практика же показывает, что даже 5 кнопок можно очень компактно разместить.. Тем более одну;)
На данный момент ЗУ остается на той же аппаратной основе, программно универсальность пока убрана, поскольку возникла необходимость работы конкретно с автомобильными АКБ 12V не первой свежести.
Так что возможно пора открывать новую тему, адаптивного зарядного устройства для автомобильных аккумуляторов. Со свойственной им спецификой заряда.
Старый скетч универсала в посте . Схема ниже.
ЗЫ: А защиту от переполюсовки на мосфете из предыдущей версии схемы придется вернуть - сегодня сжег шунт тока заряда..
Добрый день, уважаемый Домосед.
Вопрос-скетч возможно ли использовать для гелевых 12В 5-7А/ч? или нужны изменения? Если да, то какие?
Добрых дней! Кто бы дал взаймы свободного времени ;( .. D)
К сожалению предыдущая схема нуждается в модификации, в целях безопасности. Поскольку импульсы тока на выходе могут привести к неприятным последствиям при неаккуратном обращении с ЗУ. Бабах в общем. И все сульфаты вместе с кислотой оказываются на стенах и потолке.
Вариант модернизации - 1
Вариант - 2 ( более действенный, но небезопасный)
Поскольку данная схемная реализация не оправдала надежд на эффективное восстановление б/у АКБ, буду её менять.
Скетч к варианту 1 прилагаю, безусловно он сырой и нуждается в доработке. Через пару дней его поправлю для удобоваримости неискушенных пользователей.
Касательно гелевых и AGM АКБ - это всё те же свинцово-кислотные аккумуляторы, с присущей им некоторой спецификой обслуживания и параметров заряда. В сети имеется необходимая по этому поводу информация.
P.S. БУДЬТЕ ВНИМАТЕЛЬНЫ И ОСТОРОЖНЫ ПРИ РАБОТЕ С ПРОБЛЕМНЫМИ АКБ!!!
Вот казалось бы 10 лет АКБ - ему жить и жить, но режима перемешивания электролита при 16,2 вольта при больших импульсных токах (средневыпрямленный ток небольшой), не выдержал. Хорошо обошлось без травм и химических ожогов, но обстановку в помещении попортил.. Правда присутствовал некоторый локальный нагрев в верхней части 4 банки, наверно локальное неполное КЗ.
Так что вот, берегите себя и окружающих!!!
Не удалось избавиться от самовозбуждения разрядного мосфета.
Который изрядно при этом нагревался, заставляя вентилятор набирать обороты.
Поставил в разрядную цепь мощный биполярный транзистор (Ik>10Ампер, h21э>20).
Теперь всё работает как часы :)