Универсальное зарядное устройство, для любых типов аккумулятор, свинцовых, Li-Ion и пр.

Domosed
Offline
Зарегистрирован: 15.07.2019

Oleg Smirnoff пишет:

Не хочу подрывать ваш энтузиазм - но вот на моей памяти - еще с тех лет когда я школьником читал свой любимый журнал "Радио" - множество людей пытались добиться "ясности" в этом неблагодарном деле "восстановления автомобильных АКБ".. Всех благ - и успеха !

Нонсенс, но в большинстве случаев дорога к цели или постижение "ясности", гораздо более интересны, чем обладание окончательным результатом. ;)
Такова наверно, суть творческого потенциала человека. Если он имеется конечно и есть возможность его реализации.
А в остальном, ситуация по алгоритмам пока в стадии - "чем дальше в лес, тем толще партизаны" :D
 

vennn3
Offline
Зарегистрирован: 24.08.2015

Добрый день, уважаемый Домосед.

Подскажите:

1.как можно правильно подсоединить энкодер вместо кнопок? (нет места в коробочке)

2.я правильно понял, что последние: схема 05/08/2022 - 22:48, скетчи 10/08/2022 - 23:24?

Domosed
Offline
Зарегистрирован: 15.07.2019

vennn3 пишет:
Добрый день, уважаемый Домосед..

Доброго дня! К сожалению в своих поделках до текущего момента обходился без энкодеров. Наверно потому, что у меня кнопок этих, ну просто завались :D И к сожалению пока не планирую отходить от этой традиции. Тем более, что это потребует существенной переделки кода, на что просто нет времени. Практика же показывает, что даже 5 кнопок можно очень компактно разместить.. Тем более одну;)
На данный момент ЗУ остается на той же аппаратной основе, программно универсальность пока убрана, поскольку возникла необходимость работы конкретно с автомобильными АКБ 12V не первой свежести.
Так что возможно пора открывать новую тему, адаптивного зарядного устройства для автомобильных аккумуляторов. Со свойственной им спецификой заряда.

Старый скетч универсала в посте . Схема ниже.

ЗЫ: А защиту от переполюсовки на мосфете из предыдущей версии схемы придется вернуть - сегодня сжег шунт тока заряда.. 

 

vennn3
Offline
Зарегистрирован: 24.08.2015

Добрый день, уважаемый Домосед.

Вопрос-скетч возможно ли использовать для гелевых 12В 5-7А/ч? или нужны изменения? Если да, то какие?

Domosed
Offline
Зарегистрирован: 15.07.2019

vennn3 пишет:
Добрый день, уважаемый Домосед. Вопрос-скетч возможно ли использовать для гелевых 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 банки, наверно локальное неполное КЗ.

Так что вот, берегите себя и окружающих!!!

Domosed
Offline
Зарегистрирован: 15.07.2019

Не удалось избавиться от самовозбуждения разрядного мосфета.
Который изрядно при этом нагревался, заставляя вентилятор набирать обороты.
Поставил в разрядную цепь мощный биполярный транзистор (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;
}