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

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

Предлагаю нативному вниманию внеочередную версию зарядного устройства, с поддержкой любых типов аккумуляторов и режимов заряда-разряда и скрытым, нативным режимом десульфатации. 

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

На настоящем этапе конструкция не облагорожена, посему слабонервным, просьба на фото не смотреть.
Схема:

Заряд.разряд осуществляется высокотоковыми мосфетами с регулируемой Ардуиной ШИМ, с контролем наряжения на акб и токов заряда/разряда. Питание зарядного, внешнее, рекомендую БП 17..19V соответствующего тока.


Фото в работе на свинцовый автомобильный аккумулятор и Li-Ion 18650:

Поддерживаемые на текущий момент режимы:
Активация, CC и СV заряд/разряд, разряд, однократные либо циклические, нативная десульфатация свинцовых АКБ и декристаллизация Li-Ion :O
Поддерживаемые на текущий момент типы аккумуляторов:
Свинцовые кислотные 6..12V, LiIon, LiFePO4, LiPO и тп. Планирую добавить по возможности щелочные.
В процессе работы контролируются:
Напряжение на АКБ, ток заряда/разряда, полученная и отданная энергия в ампер.часах, время работы.
В меню выбирается режим работы, вручную настраиваются параметры режимов.
После включения, возможен запуск автоматического режима с настройками по умолчанию для 12V АКБ 60Ah. 

Скетч прилагается, экзальтированным особам просьба воздержаться от просмотра..
 

/**********************************************************************************
 * DomosedUnlimitedSoftware present. FREE, GNU, PNU licensing 2022(:)).
 **********************************************************************************
 * Заряд свинцовых 6V 12V и LiIon аккумуляторов  
 * !!! Если платформа не GyverCore закоментировать 	analogPrescaler( 64);
 * !!! НЕ ДОПУСКАТЬ ПЕРЕПОЛЮСОВКИ ПИТАНИЯ И АККУМУЛЯТОРА !!!   
 * !!! ПЕРЕД ЗАПУСКОМ ЗАРЯДА РЕКОМЕНДУЮ ПРОВЕРИТЬ ПАРАМЕТРЫ !!!   
 * !!! Все наряжения и токи в сотых долях, т.е. U 1240 = 12.40V, I 150 = 1.5A
 * В доработку:
 *		связность экранов и пунктов меню
 * 		период вывода в UART и информативность
 * 		расширить функциональность ( допрежимы + автоопределение АКБ 12V, 6V, LiIon)
 *		добавить поддержку щелочных аккумуляторов
 *		добавить светоиндикацию фаз работы светодиодами
 *		поддержка циклов
 *		запись параметров в EEPROM 
 *		хотелось бы вайфай :|	
 *		и тд и тп
 *********************************************************************************/
#include <Wire.h>
#include <GyverPWM.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.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 pinLedWork 13
#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 MODE_CC_CHARGE			1
#define MODE_CV_CHARGE			2
#define MODE_CC_CHARGE_DISHARGE	3
#define MODE_CV_CHARGE_DISHARGE 4
#define MODE__DISHARGE 			5

uint8_t operatingMode = MODE_CC_CHARGE;
/***************** фазы работы **********************/
#define STOP 		0   // стоп 
#define ACTIVATION  1   // активация аккумулятора
#define CHARGE      2   // заряд
#define CH_PAUSE    3   // пауза после заряда
#define DISCHARGE   4   // разряд
#define DSC_PAUSE   5   // пауза после разряда

uint8_t workPhase = STOP;
uint8_t	prevWorkPhase; 
/***************** фреймы экрана **********************/
#define FRAME_INFO 		0	// информационный 
#define FRAME_VOLTAGE	1	// меню установки напряжений
#define FRAME_CURRENT	2	// меню установки токов
#define FRAME_MODE		11	// меню выбора режима работы
#define FRAME_START		12	// старт/стоп

uint8_t curFrame = FRAME_INFO;
uint8_t prevCurFrame;		
uint8_t markedLine = 1;		// пункт меню
uint8_t markedLineMode = 1;	// пункт меню режимов

bool fEdit = false;			// флаг редактирования параметров
/******************** Настроечные **********************/
int16_t setActVoltage = 1200;       // напряжение окончания активации 12.00V
int16_t setMinVoltage = 1250;       // минимальное напряжение для разряда 12.50V
int16_t setMaxVoltage = 1420;       // максимальное напряжение для заряда 14.20V
int16_t setSaveVoltage = 1275;      // напряжение хранения 12.75V

int16_t setActChargeCurrent = 50;   // ток активационного заряда 0.5A
int16_t setChargeCurrent = 150;   	// ток заряда	1.5A
int16_t setCutChargeCurrent = 40;   // ток отсечки заряда CV 0,4A
int16_t setDischargeCurrent = 150;	// ток разряда	1.5A

bool 	fLiIon = false;				// LiIon аккумулятор
uint8_t nCycles = 1;				// число циклов заряд/разряд
/******************** Действующие **********************/
int16_t workActChargeCurrent;   	// ток активационного заряда, рабочий
int16_t workChargeCurrent;   		// ток заряда, рабочий
int16_t workCutChargeCurrent;		// ток отсечки заряда CV, рабочий
int16_t workDischargeCurrent;		// ток разряда, рабочий

int16_t workVoltagePause;			// пауза, до падения напряжения
/****************** Рабочие, текущие *******************/
int16_t BatVoltage;				// текущее напряжение батареи
int16_t ChargeCurrent;			// текущий ток заряда			
int16_t DischargeCurrent;		// текущий ток разряда

//int32_t chargeEnergy;			// полученная энергия при заряде Ам.час
//int32_t dischargeEnergy;		// отданная энергия при разряде Ам.час
float chargeEnergy;             // полученная энергия при заряде Ам.час
float dischargeEnergy;          // отданная энергия при разряде Ам.час

uint16_t pwmCharge = 0;			// ШИМ заряда
uint16_t pwmDischarge = 0;		// ШИМ разряда

uint32_t onTime = 0;			// время от включения прибора, секунды
uint32_t TimeWork = 0;			// время работы сеанса, секунды
uint32_t prevTime = 0;			// 
uint16_t timeHours;				// часы
uint16_t timeMins;				// минуты
uint16_t timeSecs;				// секунды

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( pinLedWork, 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);

	workActChargeCurrent = setActChargeCurrent;	// ток активации 
	workChargeCurrent = setChargeCurrent;		// ток заряда
	workCutChargeCurrent = setCutChargeCurrent;	// ток отсечки заряда CV
	workDischargeCurrent = 0;					// ток разряда
	workVoltagePause = setSaveVoltage;			// напряжение хранения	
}

void loop() 
{ 
	static unsigned long dt;			// время loop
	
	prevWorkPhase = workPhase;			// предыдущий режим
	procPresButtons();					// обработка по нажатию кнопок
	aplySettings( fEdit);				// применить изменение параметров
	getTimeWork();						// получить время работы
	getVoltageCurrent();				// получить напряжение батареи, токи заряда, разряда
	energyCalculation();				// подсчет полученной, отданной энергии
	displayFrame( curFrame, markedLine);// вывод на дисплей
	
	/***************** отработка режимов *****************/
	switch ( workPhase) 
	{
		case STOP:				// останов
			ChargingBattery( 0, 0, 0);
			DischargingBattery( 0, 0);
			makePause( 0, 0);
			break;
		case ACTIVATION:		// активация батареи
			if ( BatVoltage < setActVoltage) { 
				if ( ChargingBattery( workActChargeCurrent, 0, setActVoltage)) 
					workPhase = CHARGE; 
			} 
			else workPhase = CHARGE;
			break;
		case CHARGE:			// заряд батареи
			if ( ChargingBattery( workChargeCurrent, workCutChargeCurrent, setMaxVoltage))
				workPhase = CH_PAUSE; 
			break;
		case CH_PAUSE:			// пауза
			if ( makePause( 10, 0)) 
				workPhase = DISCHARGE;
			break;
		case DISCHARGE:			// разряд постоянным током
			if ( DischargingBattery( workDischargeCurrent, setMinVoltage)) 
				workPhase = DSC_PAUSE;
			break;
		case DSC_PAUSE:			// пауза
			if ( makePause( 10, workVoltagePause)) workPhase = ACTIVATION;
			break;
	}
	dt = millis() - dt;			// длительность loop
	//if ( dt > 50)               // вывод при обновлении дисплея, когда dt>50ms
	//	Serial << F("U=") << BatVoltage << F(" I=") << ChargeCurrent << '/' << DischargeCurrent 
	//			<< F(" PWM=") << pwmCharge << '/' << pwmDischarge << F(" dT") << dt << F(" En=") << chargeEnergy << '/' << dischargeEnergy << '\n';
	dt = millis();
}
/*****************************************************
 * Сохранить измененные параметры настроек
 * void aplySettings( bool edit)
*****************************************************/
void aplySettings( bool edit)
	{
		if ( edit) {
			workActChargeCurrent = setActChargeCurrent;	// ток активации 
			workChargeCurrent = setChargeCurrent;		// ток заряда
			workCutChargeCurrent = setCutChargeCurrent;	// ток отсечки заряда CV
			workDischargeCurrent = setDischargeCurrent;	// ток разряда
			workVoltagePause = setSaveVoltage;			// напряжение хранения	
		}
	}	
/*****************************************************
 * Подсчет полученной, отданной энергии в ампер часах
 * energyCalculation()
*****************************************************/
void energyCalculation(){
  if ( onTime > prevTime) { 
    prevTime = onTime;
    //chargeEnergy += ( (uint32_t)ChargeCurrent << 16) / 3600;
    //dischargeEnergy += ( (uint32_t)DischargeCurrent << 16) / 3600;
    chargeEnergy += ((float)ChargeCurrent) / 360000.0;
    dischargeEnergy += ((float)DischargeCurrent) / 360000.0;	
    }
}
/*****************************************************
 * сделать паузу по времени или падению напряжения
 * bool makeРause(uint32_t tp, int16_t voltage)
 * 		tp - время паузы в секундах, если 
 *		voltage - не равно 0, по падению напряжения
 *		ниже voltage * 
 * 		return false - пауза не окончена  
 * 		return true  - пауза закончилась  
 ****************************************************/
 bool makePause( uint32_t tpause, int16_t voltage)
 {
    static uint32_t tpStart = 0;
	if ( voltage)
		if ( BatVoltage >= voltage) return false;
		else return true;
    if ( !tpause) { 
      tpStart = 0;
      return true;
    }
    if ( !tpStart) tpStart = TimeWork;
    if (( TimeWork - tpStart) >= tpause) 
    {
      tpStart = 0;
      return true;
    }
    else return false;  
 }
/*************************************************************************************
 * Заряд аккумулятора СС - постоянным током до достижения voltage на аккумуляторе.
 * Если mincurrent не равен 0, то после достижения voltage, 
 * заряд CV - падающим током до mincurrent
 * 
 * bool ChargingBattery( uint16_t constcurrent, uint16_t mincurrent, uint16_t maxvoltage) 
 *				constcurrent - ток заряда Амперы
 *        mincurrent - min ток поддержания заряда
 *        voltage - предел заряда по напряжению
 * return false - заряд продолжается
 *        true  - по окончанию заряда
 ****************************************************/
bool ChargingBattery( uint16_t constcurrent, uint16_t mincurrent, uint16_t maxvoltage)
{
	uint16_t dPwm;	 // дельта приращения pwm
	static bool fCV = false;
 
	/* если constcurrent=0, или достигнуто напряжение, при 0 mincurrent - останов заряда */
	if ( constcurrent == 0 || (( BatVoltage >= maxvoltage) && ( mincurrent == 0))) {
endcharge:    		
		pwmCharge =	0;
		fCV = false;
		PWM_16KHZ_D10(pwmCharge); 
		PWM_detach(pinPwmCharge);
		return true; 
		}
   
	if ( pwmCharge == 0) PWM_attach(pinPwmCharge);	// включить ШИМ заряда
	if ( fCV || fLiIon) dPwm = 1;					// если CV поддержание или LiIon
	  else if ( pwmCharge < 200) dPwm = 10;
		  else if ( pwmCharge < 500) dPwm = 5;
			  else if ( pwmCharge < 700) dPwm = 2;
				  else if ( pwmCharge < 900) dPwm = 2;
					  else if ( pwmCharge < 1000) dPwm = 1;
						  else dPwm = 1;
  
  if ( BatVoltage >= maxvoltage) 
  {
    fCV = true;
    if ( ChargeCurrent > mincurrent)
    {
      if ( pwmCharge > dPwm) {
        pwmCharge -= dPwm;
        goto retcharge;
      }
    } else goto endcharge;
  }
  if (( BatVoltage < (maxvoltage)) && fCV)
    pwmCharge += dPwm;
      
  if ( !fCV) {                	 
	  if ( ChargeCurrent < constcurrent) {
		  if ( pwmCharge < 1023) pwmCharge += dPwm;
	  } 
	  else 
	  {
		  if ( ChargeCurrent > (constcurrent))
		  	if ( pwmCharge > dPwm) pwmCharge -= dPwm;
	  }
  }
retcharge: 
	PWM_16KHZ_D10(pwmCharge);
  return false;
}
/*****************************************************
 * Разряд аккумулятора постоянным током current
 * bool DischargingBattery( uint16_t current, uint16_t voltage) 
 *				current - ток разряда Амперы
 *        voltage - предел разряда по напряжению
 * return false  - если напряжение не занижено
 *        true - по окончанию разряда
*****************************************************/
bool DischargingBattery( uint16_t current, uint16_t voltage)
{
	uint16_t dPwm;	 // дельта приращения pwm
	
	if ( current == 0 || BatVoltage <= voltage) {		// если ток=0, или занижено напряжение - останов заряда
		pwmDischarge =	0;
		PWM_16KHZ_D9(pwmDischarge); 
		PWM_detach(pinPwmDischarge);
		return true; 
		}
	
	if ( pwmDischarge == 0) PWM_attach(pinPwmDischarge);	// включить ШИМ разряда
	if ( pwmDischarge < 200) dPwm = 10;
		else if ( pwmDischarge < 500) dPwm = 5;
			else if ( pwmDischarge < 700) dPwm = 2;
				else if ( pwmDischarge < 900) dPwm = 2;
					else if ( pwmDischarge < 1000) dPwm = 1;
						else dPwm = 1;	 
	if ( DischargeCurrent < current) {
		if ( pwmDischarge < 1023) pwmDischarge += dPwm;
	} else {
		if ( DischargeCurrent > (current + 2))
			if ( pwmDischarge >dPwm) pwmDischarge -= dPwm;
	}
	PWM_16KHZ_D9( pwmDischarge);
  return	 false;
}
/*****************************************************
 * getVoltageCurrent() - считать напряжение и токи
*****************************************************/
void getVoltageCurrent(){
	uint16_t buf;
	{ // напряжение на батарее 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_Voltage);
		}
		buf = buf / 16;
		BatVoltage = uint16_t(buf) * 25 /10;
	}	
	{ // ток заряда 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_ChargeCurrent);
		}
		buf = buf / 16;
		ChargeCurrent = buf * 3;  // шунт - 3 резистора 0.1
	}
	{ // ток разряда 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_DischargeCurrent);
		}
		buf = buf / 16;
		DischargeCurrent = buf * 2;  // шунт - 2 резистора 0.1 в параллель
	}
	BatVoltage += ((-ChargeCurrent)/30 + DischargeCurrent/20); // 3 и 2 число резисторов по 0.1 ома
}
/*****************************************************
 * getTimeWork() - время работы сеанса
*****************************************************/
void getTimeWork()
{
	static bool fPrevStart = false;
	static uint32_t timeStart = 0;						// время старта работы
	onTime = millis() / 1000ul;							// время от включения прибора
	if ( (workPhase != STOP) && !fPrevStart) {	 
		fPrevStart = true;
		timeStart = millis();
	}
	if ( (workPhase != STOP)) {						// если не останов
		TimeWork = (millis() - timeStart) / 1000ul;		// полное количество секунд работы	
	} else {
		TimeWork = 0;
		timeStart = 0;
		fPrevStart = false; 
	}	
	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) >= 250) 		// обновление экрана 4 раза в секунду
		tDisplay = ttt;
	else return;
	
	display.setTextSize( 2);
	display.clearDisplay();
	display.setCursor(0,0);
	//Serial << "frame " << frame << "\n";
	if ( frame == FRAME_INFO) {
		int16_t current = ( ChargeCurrent > DischargeCurrent ? ChargeCurrent : -DischargeCurrent);
		sprintf(bufStr, "Vb %02d.%02dv", BatVoltage/100, BatVoltage%100);
		display.println(bufStr);
		sprintf(bufStr, "Ic %02d.%02da", current/100, abs(current)%100);
		display.println(bufStr); 
		if ( (( TimeWork + 1)%3) ) {
			sprintf(bufStr, "Pwm %04d", pwmCharge > pwmDischarge ? pwmCharge : -pwmDischarge);
			display.println(bufStr);
			sprintf(bufStr, "T%3d:%02d:%02d", timeHours, timeMins, timeSecs);	
			display.println(bufStr);
		} 
		else {		
			//sprintf(bufStr, "+ %d.%02dah", (chargeEnergy >> 16) / 100, ( (chargeEnergy >> 16) / 100)%100);
			//display.println(bufStr);
			//sprintf(bufStr, "- %d.%02dah", (dischargeEnergy >> 16) /100, ( (dischargeEnergy >> 16) /100)%100);
			//display.println(bufStr);
			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);
		}
	} 
	else if ( frame == FRAME_START) {
		sprintf(bufStr, "Vb %02d.%02dv", BatVoltage/100, BatVoltage%100);
		display.println(bufStr);		
		display.setTextSize( 3);
		display.setCursor(0,24);
		if ( workPhase != STOP) display.println( F(" STOP?"));
		else display.println( F(" START?"));
	} 
	else if ( frame == FRAME_MODE) {
		if ( markedLineMode < 5) {
			display.print( F(" CC charge"));
			display.print( F(" CV charge"));
			display.print( F(" CC ch/dis"));
			display.print( F(" CV ch/dis"));
			display.setCursor( 0, ( markedLineMode -1 ) *16);			
		} else if ( markedLineMode < 9) {
			display.print( F(" Discharge"));
			display.print( F(" LiIon chg"));
			display.print( F(" LiIon sav"));
			display.print( F(" LiIon dis"));
			display.setCursor( 0, ( markedLineMode -5 ) *16);
		}
		display.print( F("> "));		
	} 
	else if ( frame == FRAME_VOLTAGE) {
		display.print( F(" Uact")); display.print( (float)setActVoltage / 100.0);
		if ( setActVoltage < 1000) display.print( F( "\n"));
		display.print( F(" Umin")); display.print( (float)setMinVoltage / 100.0);
		if ( setMinVoltage < 1000) display.print( F( "\n"));		
		display.print( F(" Umax")); display.print( (float)setMaxVoltage / 100.0);
		if ( setMaxVoltage < 1000) display.print( F( "\n"));
		display.print( F(" Usav")); display.print( (float)setSaveVoltage / 100.0);
		if ( setSaveVoltage < 1000) display.print( F( "\n"));
		display.setCursor( 0, ( marked -1 ) *16);
		if ( fEdit) display.print( F("* "));
		else display.print( F("> "));
	} 
	else if ( frame == FRAME_CURRENT) {
		display.print( F(" Ichg ")); display.print( (float)setChargeCurrent / 100.0);
		display.print( F(" Icut ")); display.print( (float)setCutChargeCurrent / 100.0);
		display.print( F(" Idis ")); display.print( (float)setDischargeCurrent / 100.0);
		display.print( F(" cycles ")); display.print( nCycles);
		display.setCursor( 0, ( marked -1 ) *16);
		if ( fEdit) display.print( F("* "));
		else display.print( F("> "));
	} 
	display.display();
}
/****************************************************
 * uint8_t getButtonState() опрос кнопок
*****************************************************/
uint8_t getButtonState()
{	
	static uint32_t pressTime = 0;         // время нажатия кнопки
	static uint8_t button;
	uint8_t state = (~PIND & 0b1111100) >> 2;
	if ( state && !pressTime) 
	{ 
		pressTime = millis();
		button = state;
	}
	if ( !state && pressTime) 
	{
		if ( ( millis() - pressTime) > 300)
		{
			pressTime = 0;
			return button;
		}
	}
	return 0;
}
/*****************************************************
 * Обработка по нажатию кнопок
 * void procPresButtons()
*****************************************************/
void procPresButtons()
{
	uint8_t button = getButtonState();
	
	switch ( button)
	{
		case BUT_OK:	// переход на выбор режима работы с последующим стартом или останов
			if ( curFrame == FRAME_INFO) {
				if ( workPhase != STOP) curFrame = FRAME_START;
				else curFrame = FRAME_MODE;				
			}
			else if ( curFrame == FRAME_START) {
				if ( workPhase != STOP) 
				{	
					workPhase = STOP;		
					digitalWrite(pinLedWork, 0);
				} else {
					chargeEnergy = 0;
					dischargeEnergy = 0;
					workPhase = ACTIVATION;
					digitalWrite(pinLedWork, 1); 
				}
				curFrame = FRAME_INFO;
			}
			else if ( curFrame == FRAME_MODE) {	// выбор режима перед стартом
				
				if ( markedLineMode == 1) {	// CC ch save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = 0;
					workDischargeCurrent = 0;
					workVoltagePause = setSaveVoltage;
				}
				if ( markedLineMode == 2) { // CV ch save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = setCutChargeCurrent;
					workDischargeCurrent = 0;
					workVoltagePause = setSaveVoltage;					
				}
				if ( markedLineMode == 3) { // CC ch/dis no save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = 0;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 4) { // CV ch/dis no save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = setCutChargeCurrent;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 5) { // discharge 
					workActChargeCurrent = 0;
					workChargeCurrent = 0;
					workCutChargeCurrent = 0;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 6) { 	// LiIon charge no save
					setActVoltage = 290;		// напряжение окончания активации 2.9V
					setMinVoltage = 360;		// минимальное напряжение для разряда 3.6V
					setMaxVoltage = 420;		// максимальное напряжение для заряда 4.20V
					setSaveVoltage = 380;		// напряжение хранения 3.80V

					setActChargeCurrent = 10;	// ток активационного заряда 0.1A
					setChargeCurrent = 50;		// ток заряда	0.5A
					setCutChargeCurrent = 1;	// ток отсечки заряда CV 0,01A
					setDischargeCurrent = 50;	// ток разряда	0.5A

					workActChargeCurrent = setActChargeCurrent;	// ток активации 
					workChargeCurrent = setChargeCurrent;		// ток заряда
					workCutChargeCurrent = setCutChargeCurrent;	// ток отсечки заряда CV
					workDischargeCurrent = setDischargeCurrent;	// ток разряда
					workVoltagePause = setSaveVoltage;			// напряжение хранения						
					
					fLiIon = true;								// LiIon battery
				}				
				if ( markedLineMode == 6 ) {
					markedLineMode = 1;			// 
					curFrame = FRAME_MODE; 
				} else curFrame = FRAME_START;
			}
			else if ( curFrame == FRAME_VOLTAGE || curFrame == FRAME_CURRENT)	
				fEdit = !fEdit;
			break;

		case BUT_RIGHT:
			if ( workPhase == STOP && !fEdit) {
				markedLine = 1;
				if ( curFrame == FRAME_CURRENT) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_MODE) curFrame = FRAME_INFO;
					else curFrame++;
			} 
			else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
			break;
		case BUT_LEFT:
			if ( workPhase == STOP && !fEdit) {
				markedLine = 1;
				if ( curFrame == FRAME_INFO) curFrame = FRAME_CURRENT;
				else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_MODE) curFrame = FRAME_INFO;
					else curFrame--; 
			} 
			else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
			break;

		case BUT_UP:
		case BUT_DOWN:
			Serial << F("BUT_UP_DOUN") << "\n";
			if ( fEdit) {
				if ( curFrame == FRAME_VOLTAGE){
					if ( markedLine == 1){
						button == BUT_UP ? setActVoltage += 10 : setActVoltage -= 10;
						if ( setActVoltage < 290) setActVoltage = 290;
						else if ( setActVoltage > 1200) setActVoltage = 1200;
					}
					if ( markedLine == 2){
						button == BUT_UP ? setMinVoltage += 10 : setMinVoltage -= 10;
						if ( setMinVoltage < 350) setMinVoltage = 350;
						else if ( setMinVoltage > 1250) setMinVoltage = 1250;
					}
					if ( markedLine == 3){
						button == BUT_UP ? setMaxVoltage += 10 : setMaxVoltage -= 10;
						if ( setMaxVoltage < 420) setMaxVoltage = 420;
						else if ( setMaxVoltage > 1620) setMaxVoltage = 1620;
					}
					if ( markedLine == 4){
						button == BUT_UP ? setSaveVoltage += 10 : setSaveVoltage -= 10;
						if ( setSaveVoltage < 380) setSaveVoltage = 380;
						else if ( setSaveVoltage > 1280) setSaveVoltage = 1280;
					}					
				}
				if ( curFrame == FRAME_CURRENT){
					if ( markedLine == 1){
						button == BUT_UP ? setChargeCurrent += 10 : setChargeCurrent -= 10;
						if ( setChargeCurrent < 10) setChargeCurrent = 10;
						else if ( setChargeCurrent > 500) setChargeCurrent = 500;
					}
					if ( markedLine == 2){
						button == BUT_UP ? setCutChargeCurrent += 10 : setCutChargeCurrent -= 10;
						if ( setCutChargeCurrent < 10) setCutChargeCurrent = 10;
						else if ( setCutChargeCurrent > 100) setCutChargeCurrent = 100;
					}
					if ( markedLine == 3){
						button == BUT_UP ? setDischargeCurrent += 10 : setDischargeCurrent -= 10;
						if ( setDischargeCurrent < 10) setDischargeCurrent = 10;
						else if ( setDischargeCurrent > 300) setDischargeCurrent = 300;
					}
					if ( markedLine == 4){
						button == BUT_UP ? nCycles++ : nCycles--;
						if ( nCycles < 1) nCycles = 1;
						else if ( nCycles > 99) nCycles = 99;
					}					
				}					
			}			
			if ( curFrame > FRAME_INFO && curFrame < FRAME_MODE && !fEdit) {
				button == BUT_UP ? markedLine-- : markedLine++;
				if ( !markedLine) markedLine++;
				else if ( markedLine > 4) markedLine--;
			} 
			else if ( curFrame == FRAME_MODE) {
				Serial << F("BUT_UP_DOWN_FRAME_MODE") << "\n";
				button == BUT_UP ? markedLineMode-- : markedLineMode++;
				if ( !markedLineMode) markedLineMode++;
				else if ( markedLineMode > 6) markedLineMode--;
			}
			break;
	}
}

Скетч естественно нуждается в чистке и доработке.
Проект некоммерческий, посему любая помощь  в отладке и доработке кода и тп. будет приветствоваться прямо в карму :) Сам сейчас буду доводить до приемлемого вида аппаратную составляющую, поскольку програмная компонента меня почти устраивает.

SLKH
Offline
Зарегистрирован: 17.08.2015

Domosed пишет:

На настоящем этапе конструкция не облагорожена, посему слабонервным, просьба на фото не смотреть.
Схема:

 

Диоды от переполюсовки (зеленые), имхо, необходимы. Ну и предохранитель на входе питания, для порядка.

Пунктирную цепь с диодом vd1 я не понял. 

Мобыть есть смысл сразу добавить и автоуправление вентилятором - от тока или температуры?

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

ну и выравнивание по ячейкам необходимо, короче делаем IMAX-6 но на ток до 10 ампер

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

SLKH пишет:

Пунктирную цепь с диодом vd1 я не понял. 
Мобыть есть смысл сразу добавить и автоуправление вентилятором - от тока или температуры?

Если запитывать Ардуину, не от внешнего источника, а от аккумулятора 6..12В, то использовать пунктирную цепь. 
Да, управление вентилятором проще сделать по току заряда/разряда, например >2A
ua6em пишет:
ну и выравнивание по ячейкам необходимо, короче делаем IMAX-6 но на ток до 10 ампер

Можно конечно, но изначальная идея - создание универсального устройства для заряда и обслуживания  автомобильных аккумуляторов 12V

Поправил скетч, был баг с циклами и отображением полученной/отданной емкости:
 

/**********************************************************************************
 * DomosedUnlimitedSoftware present. FREE, GNU, PNU licensing 2022(:)).
 **********************************************************************************
 * Заряд свинцовых 6V 12V и LiIon аккумуляторов  
 * !!! Если платформа не GyverCore закоментировать 	analogPrescaler( 64);
 * !!! НЕ ДОПУСКАТЬ ПЕРЕПОЛЮСОВКИ ПИТАНИЯ И АККУМУЛЯТОРА !!!   
 * !!! ПЕРЕД ЗАПУСКОМ ЗАРЯДА РЕКОМЕНДУЮ ПРОВЕРИТЬ ПАРАМЕТРЫ !!!   
 * !!! В скетче все наряжения и токи в сотых долях, т.е. U 1240 = 12.40V, I 150 = 1.5A
 * В доработку:
 *		связность экранов и пунктов меню
 * 		период вывода в UART и информативность
 * 		расширить функциональность ( допрежимы + автоопределение АКБ 12V, 6V, LiIon)
 *		добавить поддержку щелочных аккумуляторов
 *		добавить светоиндикацию фаз работы светодиодами
 *		запись параметров в EEPROM 
 *		хотелось бы вайфай :|	
 *		и тд и тп
 *********************************************************************************/
#include <Wire.h>
#include <GyverPWM.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.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 pinLedWork 13
#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 MODE_CC_CHARGE			1
#define MODE_CV_CHARGE			2
#define MODE_CC_CHARGE_DISHARGE	3
#define MODE_CV_CHARGE_DISHARGE 4
#define MODE__DISHARGE 			5

uint8_t operatingMode = MODE_CC_CHARGE;
/***************** фазы работы **********************/
#define STOP 		0   // стоп 
#define ACTIVATION  1   // активация аккумулятора
#define CHARGE      2   // заряд
#define CH_PAUSE    3   // пауза после заряда
#define DISCHARGE   4   // разряд
#define DSC_PAUSE   5   // пауза после разряда

uint8_t workPhase = STOP;
uint8_t	prevWorkPhase; 
/***************** фреймы экрана **********************/
#define FRAME_INFO 		0	// информационный 
#define FRAME_VOLTAGE	1	// меню установки напряжений
#define FRAME_CURRENT	2	// меню установки токов
#define FRAME_MODE		11	// меню выбора режима работы
#define FRAME_START		12	// старт/стоп

uint8_t curFrame = FRAME_INFO;
uint8_t prevCurFrame;		
uint8_t markedLine = 1;		// пункт меню
uint8_t markedLineMode = 1;	// пункт меню режимов

bool fEdit = false;			// флаг редактирования параметров
/******************** Настроечные **********************/
int16_t setActVoltage = 1200;       // напряжение окончания активации 12.00V
int16_t setMinVoltage = 1250;       // минимальное напряжение для разряда 12.50V
int16_t setMaxVoltage = 1420;       // максимальное напряжение для заряда 14.20V
int16_t setSaveVoltage = 1275;      // напряжение хранения 12.75V

int16_t setActChargeCurrent = 50;   // ток активационного заряда 0.5A
int16_t setChargeCurrent = 150;   	// ток заряда	1.5A
int16_t setCutChargeCurrent = 40;   // ток отсечки заряда CV 0,4A
int16_t setDischargeCurrent = 150;	// ток разряда	1.5A

bool 	fLiIon = false;				// LiIon аккумулятор
uint8_t nCycles = 1;				// число циклов заряд/разряд
/******************** Действующие **********************/
int16_t workActChargeCurrent;   	// ток активационного заряда, рабочий
int16_t workChargeCurrent;   		// ток заряда, рабочий
int16_t workCutChargeCurrent;		// ток отсечки заряда CV, рабочий
int16_t workDischargeCurrent;		// ток разряда, рабочий

int16_t workVoltagePause;			// пауза, до падения напряжения
uint8_t cycleCounter;				// счетчик циклов зарядов/разрядов
/****************** Рабочие, текущие *******************/
int16_t BatVoltage;				// текущее напряжение батареи
int16_t ChargeCurrent;			// текущий ток заряда			
int16_t DischargeCurrent;		// текущий ток разряда

float chargeEnergy;             // полученная энергия при заряде Ам.час
float dischargeEnergy;          // отданная энергия при разряде Ам.час

uint16_t pwmCharge = 0;			// ШИМ заряда
uint16_t pwmDischarge = 0;		// ШИМ разряда

uint32_t onTime = 0;			// время от включения прибора, секунды
uint32_t TimeWork = 0;			// время работы сеанса, секунды
uint32_t prevTime = 0;			// 
uint16_t timeHours;				// часы
uint16_t timeMins;				// минуты
uint16_t timeSecs;				// секунды

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( pinLedWork, 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);

	workActChargeCurrent = setActChargeCurrent;	// ток активации 
	workChargeCurrent = setChargeCurrent;		// ток заряда
	workCutChargeCurrent = setCutChargeCurrent;	// ток отсечки заряда CV
	workDischargeCurrent = 0;					// ток разряда
	workVoltagePause = setSaveVoltage;			// напряжение хранения	
}

void loop() 
{ 
	static unsigned long dt;			// время loop
	
	prevWorkPhase = workPhase;			// предыдущий режим
	procPresButtons();					// обработка по нажатию кнопок
	aplySettings( fEdit);				// применить изменение параметров
	getTimeWork();						// получить время работы
	getVoltageCurrent();				// получить напряжение батареи, токи заряда, разряда
	energyCalculation();				// подсчет полученной, отданной энергии
	displayFrame( curFrame, markedLine);// вывод на дисплей
	
	/***************** отработка режимов *****************/
	switch ( workPhase) 
	{
		case STOP:				// останов
			ChargingBattery( 0, 0, 0);
			DischargingBattery( 0, 0);
			makePause( 0, 0);
			break;
		case ACTIVATION:		// активация батареи
			if ( BatVoltage < setActVoltage) { 
				if ( ChargingBattery( workActChargeCurrent, 0, setActVoltage)) 
					workPhase = CHARGE; 
			} 
			else workPhase = CHARGE;
			break;
		case CHARGE:			// заряд батареи
			if ( workChargeCurrent == 0) workPhase = DISCHARGE; // заряд не нужен
			else if ( ChargingBattery( workChargeCurrent, workCutChargeCurrent, setMaxVoltage))
				workPhase = CH_PAUSE; 
			break;
		case CH_PAUSE:			// пауза
			if ( makePause( 10, 0)) 
				workPhase = DISCHARGE;
			break;
		case DISCHARGE:			// разряд постоянным током
			if ( DischargingBattery( workDischargeCurrent, setMinVoltage)){
				if ( workDischargeCurrent) cycleCounter++;
				if ( cycleCounter == nCycles) {
					workPhase = STOP;
					digitalWrite(pinLedWork, 0);
				}
				else workPhase = DSC_PAUSE;
			}
			break;
		case DSC_PAUSE:			// пауза
			if ( makePause( 10, workVoltagePause)) workPhase = ACTIVATION;
			break;
	}
	dt = millis() - dt;			// длительность loop
	//if ( dt > 50)               // вывод при обновлении дисплея, когда dt>50ms
	//	Serial << F("U=") << BatVoltage << F(" I=") << ChargeCurrent << '/' << DischargeCurrent 
	//			<< F(" PWM=") << pwmCharge << '/' << pwmDischarge << F(" dT") << dt << F(" En=") << chargeEnergy << '/' << dischargeEnergy << '\n';
	dt = millis();
}
/*****************************************************
 * Сохранить измененные параметры настроек
 * void aplySettings( bool edit)
*****************************************************/
void aplySettings( bool edit)
	{
		if ( edit) {
			workActChargeCurrent = setActChargeCurrent;	// ток активации 
			workChargeCurrent = setChargeCurrent;		// ток заряда
			workCutChargeCurrent = setCutChargeCurrent;	// ток отсечки заряда CV
			workDischargeCurrent = setDischargeCurrent;	// ток разряда
			workVoltagePause = setSaveVoltage;			// напряжение хранения	
		}
	}	
/*****************************************************
 * Подсчет полученной, отданной энергии в ампер часах
 * energyCalculation()
*****************************************************/
void energyCalculation(){
  if ( onTime > prevTime) { 
    prevTime = onTime;
    //chargeEnergy += ( (uint32_t)ChargeCurrent << 16) / 3600;
    //dischargeEnergy += ( (uint32_t)DischargeCurrent << 16) / 3600;
    chargeEnergy += ((float)ChargeCurrent) / 360000.0;
    dischargeEnergy += ((float)DischargeCurrent) / 360000.0;	
    }
}
/*****************************************************
 * сделать паузу по времени или падению напряжения
 * bool makeРause(uint32_t tp, int16_t voltage)
 * 		tp - время паузы в секундах, если 
 *		voltage - не равно 0, по падению напряжения
 *		ниже voltage * 
 * 		return false - пауза не окончена  
 * 		return true  - пауза закончилась  
 ****************************************************/
 bool makePause( uint32_t tpause, int16_t voltage)
 {
    static uint32_t tpStart = 0;
	if ( voltage)
		if ( BatVoltage >= voltage) return false;
		else return true;
    if ( !tpause) { 
      tpStart = 0;
      return true;
    }
    if ( !tpStart) tpStart = TimeWork;
    if (( TimeWork - tpStart) >= tpause) 
    {
      tpStart = 0;
      return true;
    }
    else return false;  
 }
/*************************************************************************************
 * Заряд аккумулятора СС - постоянным током до достижения voltage на аккумуляторе.
 * Если mincurrent не равен 0, то после достижения voltage, 
 * заряд CV - падающим током до mincurrent
 * 
 * bool ChargingBattery( uint16_t constcurrent, uint16_t mincurrent, uint16_t maxvoltage) 
 *				constcurrent - ток заряда Амперы
 *        mincurrent - min ток поддержания заряда
 *        voltage - предел заряда по напряжению
 * return false - заряд продолжается
 *        true  - по окончанию заряда
 ****************************************************/
bool ChargingBattery( uint16_t constcurrent, uint16_t mincurrent, uint16_t maxvoltage)
{
	uint16_t dPwm;	 // дельта приращения pwm
	static bool fCV = false;
 
	/* если constcurrent=0, или достигнуто напряжение, при 0 mincurrent - останов заряда */
	if ( constcurrent == 0 || (( BatVoltage >= maxvoltage) && ( mincurrent == 0))) {
endcharge:    		
		pwmCharge =	0;
		fCV = false;
		PWM_16KHZ_D10(pwmCharge); 
		PWM_detach(pinPwmCharge);
		return true; 
		}
   
	if ( pwmCharge == 0) PWM_attach(pinPwmCharge);	// включить ШИМ заряда
	if ( fCV || fLiIon) dPwm = 1;					// если CV поддержание или LiIon
	  else if ( pwmCharge < 200) dPwm = 10;
		  else if ( pwmCharge < 500) dPwm = 5;
			  else if ( pwmCharge < 700) dPwm = 2;
				  else if ( pwmCharge < 900) dPwm = 2;
					  else if ( pwmCharge < 1000) dPwm = 1;
						  else dPwm = 1;
  
  if ( BatVoltage >= maxvoltage) 
  {
    fCV = true;
    if ( ChargeCurrent > mincurrent)
    {
      if ( pwmCharge > dPwm) {
        pwmCharge -= dPwm;
        goto retcharge;
      }
    } else goto endcharge;
  }
  if (( BatVoltage < (maxvoltage)) && fCV)
    pwmCharge += dPwm;
      
  if ( !fCV) {                	 
	  if ( ChargeCurrent < constcurrent) {
		  if ( pwmCharge < 1023) pwmCharge += dPwm;
	  } 
	  else 
	  {
		  if ( ChargeCurrent > (constcurrent))
		  	if ( pwmCharge > dPwm) pwmCharge -= dPwm;
	  }
  }
retcharge: 
	PWM_16KHZ_D10(pwmCharge);
  return false;
}
/*****************************************************
 * Разряд аккумулятора постоянным током current
 * bool DischargingBattery( uint16_t current, uint16_t voltage) 
 *				current - ток разряда Амперы
 *        voltage - предел разряда по напряжению
 * return false  - если напряжение не занижено
 *        true - по окончанию разряда
*****************************************************/
bool DischargingBattery( uint16_t current, uint16_t voltage)
{
	uint16_t dPwm;	 // дельта приращения pwm
	
	if ( current == 0 || BatVoltage <= voltage) {		// если ток=0, или занижено напряжение - останов заряда
		pwmDischarge =	0;
		PWM_16KHZ_D9(pwmDischarge); 
		PWM_detach(pinPwmDischarge);
		return true; 
		}
	
	if ( pwmDischarge == 0) PWM_attach(pinPwmDischarge);	// включить ШИМ разряда
	if ( pwmDischarge < 200) dPwm = 10;
		else if ( pwmDischarge < 500) dPwm = 5;
			else if ( pwmDischarge < 700) dPwm = 2;
				else if ( pwmDischarge < 900) dPwm = 2;
					else if ( pwmDischarge < 1000) dPwm = 1;
						else dPwm = 1;	 
	if ( DischargeCurrent < current) {
		if ( pwmDischarge < 1023) pwmDischarge += dPwm;
	} else {
		if ( DischargeCurrent > (current + 2))
			if ( pwmDischarge >dPwm) pwmDischarge -= dPwm;
	}
	PWM_16KHZ_D9( pwmDischarge);
  return	 false;
}
/*****************************************************
 * getVoltageCurrent() - считать напряжение и токи
*****************************************************/
void getVoltageCurrent(){
	uint16_t buf;
	{ // напряжение на батарее 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_Voltage);
		}
		buf = buf / 16;
		BatVoltage = uint16_t(buf) * 25 /10;
	}	
	{ // ток заряда 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_ChargeCurrent);
		}
		buf = buf / 16;
		ChargeCurrent = buf * 3;  // шунт - 3 резистора 0.1
	}
	{ // ток разряда 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_DischargeCurrent);
		}
		buf = buf / 16;
		DischargeCurrent = buf * 2;  // шунт - 2 резистора 0.1 в параллель
	}
	BatVoltage += ((-ChargeCurrent)/30 + DischargeCurrent/20); // 3 и 2 число резисторов по 0.1 ома
}
/*****************************************************
 * getTimeWork() - время работы сеанса
*****************************************************/
void getTimeWork()
{
	static bool fPrevStart = false;
	static uint32_t timeStart = 0;						// время старта работы
	onTime = millis() / 1000ul;							// время от включения прибора
	if ( (workPhase != STOP) && !fPrevStart) {	 
		fPrevStart = true;
		timeStart = millis();
	}
	if ( (workPhase != STOP)) {						// если не останов
		TimeWork = (millis() - timeStart) / 1000ul;		// полное количество секунд работы	
	} else {
		TimeWork = 0;
		timeStart = 0;
		fPrevStart = false; 
	}	
	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) >= 250) 		// обновление экрана 4 раза в секунду
		tDisplay = ttt;
	else return;
	
	display.setTextSize( 2);
	display.clearDisplay();
	display.setCursor(0,0);
	if ( frame == FRAME_INFO) {
		int16_t current = ( ChargeCurrent > DischargeCurrent ? ChargeCurrent : -DischargeCurrent);
		sprintf(bufStr, "Vb %02d.%02dv", BatVoltage/100, BatVoltage%100);
		display.println(bufStr);
		sprintf(bufStr, "Ic %02d.%02da", current/100, abs(current)%100);
		display.println(bufStr); 
		if ( (( onTime + 1)%3) ) { // 2 секунды из 3-х
			sprintf(bufStr, "Pwm %04d", pwmCharge > pwmDischarge ? pwmCharge : -pwmDischarge);
			display.println(bufStr);
			sprintf(bufStr, "T%3d:%02d:%02d", timeHours, timeMins, timeSecs);	
			display.println(bufStr);
		} 
		else {		
			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);
		}
	} 
	else if ( frame == FRAME_START) {
		sprintf(bufStr, "Vb %02d.%02dv", BatVoltage/100, BatVoltage%100);
		display.println(bufStr);		
		display.setTextSize( 3);
		display.setCursor(0,24);
		if ( workPhase != STOP) display.println( F(" STOP?"));
		else display.println( F(" START?"));
	} 
	else if ( frame == FRAME_MODE) {
		if ( markedLineMode < 5) {
			display.print( F(" CC charge"));
			display.print( F(" CV charge"));
			display.print( F(" CC ch/dis"));
			display.print( F(" CV ch/dis"));
			display.setCursor( 0, ( markedLineMode -1 ) *16);			
		} else if ( markedLineMode < 9) {
			display.print( F(" Discharge"));
			display.print( F(" LiIon chg"));
			display.print( F(" LiIon sav"));
			display.print( F(" LiIon dis"));
			display.setCursor( 0, ( markedLineMode -5 ) *16);
		}
		display.print( F("> "));		
	} 
	else if ( frame == FRAME_VOLTAGE) {
		display.print( F(" Uact")); display.print( (float)setActVoltage / 100.0);
		if ( setActVoltage < 1000) display.print( F( "\n"));
		display.print( F(" Umin")); display.print( (float)setMinVoltage / 100.0);
		if ( setMinVoltage < 1000) display.print( F( "\n"));		
		display.print( F(" Umax")); display.print( (float)setMaxVoltage / 100.0);
		if ( setMaxVoltage < 1000) display.print( F( "\n"));
		display.print( F(" Usav")); display.print( (float)setSaveVoltage / 100.0);
		if ( setSaveVoltage < 1000) display.print( F( "\n"));
		display.setCursor( 0, ( marked -1 ) *16);
		if ( fEdit) display.print( F("* "));
		else display.print( F("> "));
	} 
	else if ( frame == FRAME_CURRENT) {
		display.print( F(" Ichg ")); display.print( (float)setChargeCurrent / 100.0);
		display.print( F(" Icut ")); display.print( (float)setCutChargeCurrent / 100.0);
		display.print( F(" Idis ")); display.print( (float)setDischargeCurrent / 100.0);
		display.print( F(" cycles ")); display.print( nCycles);
		display.setCursor( 0, ( marked -1 ) *16);
		if ( fEdit) display.print( F("* "));
		else display.print( F("> "));
	} 
	display.display();
}
/****************************************************
 * uint8_t getButtonState() опрос кнопок
*****************************************************/
uint8_t getButtonState()
{	
	static uint32_t pressTime = 0;         // время нажатия кнопки
	static uint8_t button;
	uint8_t state = (~PIND & 0b1111100) >> 2;
	if ( state && !pressTime) 
	{ 
		pressTime = millis();
		button = state;
	}
	if ( !state && pressTime) 
	{
		if ( ( millis() - pressTime) > 300)
		{
			pressTime = 0;
			return button;
		}
	}
	return 0;
}
/*****************************************************
 * Обработка по нажатию кнопок
 * void procPresButtons()
*****************************************************/
void procPresButtons()
{
	uint8_t button = getButtonState();
	
	switch ( button)
	{
		case BUT_OK:	// переход на выбор режима работы с последующим стартом или останов
			if ( curFrame == FRAME_INFO) {
				if ( workPhase != STOP) curFrame = FRAME_START;
				else curFrame = FRAME_MODE;				
			}
			else if ( curFrame == FRAME_START) {
				if ( workPhase != STOP) 
				{	
					workPhase = STOP;		
					digitalWrite(pinLedWork, 0);
				} else {
					cycleCounter = 0;		// по старту сброс накопленных показаний
					chargeEnergy = 0;
					dischargeEnergy = 0;
					workPhase = ACTIVATION;
					digitalWrite(pinLedWork, 1); 
				}
				curFrame = FRAME_INFO;
			}
			else if ( curFrame == FRAME_MODE) {	// выбор режима перед стартом
				
				if ( markedLineMode == 1) {	// CC ch save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = 0;
					workDischargeCurrent = 0;
					workVoltagePause = setSaveVoltage;
				}
				if ( markedLineMode == 2) { // CV ch save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = setCutChargeCurrent;
					workDischargeCurrent = 0;
					workVoltagePause = setSaveVoltage;					
				}
				if ( markedLineMode == 3) { // CC ch/dis no save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = 0;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 4) { // CV ch/dis no save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = setCutChargeCurrent;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 5) { // discharge 
					workActChargeCurrent = 0;
					workChargeCurrent = 0;
					workCutChargeCurrent = 0;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 6) { 	// LiIon charge no save
					setActVoltage = 290;		// напряжение окончания активации 2.9V
					setMinVoltage = 360;		// минимальное напряжение для разряда 3.6V
					setMaxVoltage = 420;		// максимальное напряжение для заряда 4.20V
					setSaveVoltage = 380;		// напряжение хранения 3.80V

					setActChargeCurrent = 10;	// ток активационного заряда 0.1A
					setChargeCurrent = 50;		// ток заряда	0.5A
					setCutChargeCurrent = 1;	// ток отсечки заряда CV 0,01A
					setDischargeCurrent = 50;	// ток разряда	0.5A

					workActChargeCurrent = setActChargeCurrent;	// ток активации 
					workChargeCurrent = setChargeCurrent;		// ток заряда
					workCutChargeCurrent = setCutChargeCurrent;	// ток отсечки заряда CV
					workDischargeCurrent = setDischargeCurrent;	// ток разряда
					workVoltagePause = setSaveVoltage;			// напряжение хранения						
					
					fLiIon = true;								// LiIon battery
				}				
				if ( markedLineMode == 6 ) {
					markedLineMode = 1;			// 
					curFrame = FRAME_MODE; 
				} else curFrame = FRAME_START;
			}
			else if ( curFrame == FRAME_VOLTAGE || curFrame == FRAME_CURRENT)	
				fEdit = !fEdit;
			break;

		case BUT_RIGHT:
			if ( workPhase == STOP && !fEdit) {
				markedLine = 1;
				if ( curFrame == FRAME_CURRENT) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_MODE) curFrame = FRAME_INFO;
					else curFrame++;
			} 
			else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
			break;
		case BUT_LEFT:
			if ( workPhase == STOP && !fEdit) {
				markedLine = 1;
				if ( curFrame == FRAME_INFO) curFrame = FRAME_CURRENT;
				else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_MODE) curFrame = FRAME_INFO;
					else curFrame--; 
			} 
			else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
			break;

		case BUT_UP:
		case BUT_DOWN:
			Serial << F("BUT_UP_DOUN") << "\n";
			if ( fEdit) {
				if ( curFrame == FRAME_VOLTAGE){
					if ( markedLine == 1){
						button == BUT_UP ? setActVoltage += 10 : setActVoltage -= 10;
						if ( setActVoltage < 290) setActVoltage = 290;
						else if ( setActVoltage > 1200) setActVoltage = 1200;
					}
					if ( markedLine == 2){
						button == BUT_UP ? setMinVoltage += 10 : setMinVoltage -= 10;
						if ( setMinVoltage < 350) setMinVoltage = 350;
						else if ( setMinVoltage > 1250) setMinVoltage = 1250;
					}
					if ( markedLine == 3){
						button == BUT_UP ? setMaxVoltage += 10 : setMaxVoltage -= 10;
						if ( setMaxVoltage < 420) setMaxVoltage = 420;
						else if ( setMaxVoltage > 1620) setMaxVoltage = 1620;
					}
					if ( markedLine == 4){
						button == BUT_UP ? setSaveVoltage += 10 : setSaveVoltage -= 10;
						if ( setSaveVoltage < 380) setSaveVoltage = 380;
						else if ( setSaveVoltage > 1280) setSaveVoltage = 1280;
					}					
				}
				if ( curFrame == FRAME_CURRENT){
					if ( markedLine == 1){
						button == BUT_UP ? setChargeCurrent += 10 : setChargeCurrent -= 10;
						if ( setChargeCurrent < 10) setChargeCurrent = 10;
						else if ( setChargeCurrent > 500) setChargeCurrent = 500;
					}
					if ( markedLine == 2){
						button == BUT_UP ? setCutChargeCurrent += 10 : setCutChargeCurrent -= 10;
						if ( setCutChargeCurrent < 10) setCutChargeCurrent = 10;
						else if ( setCutChargeCurrent > 100) setCutChargeCurrent = 100;
					}
					if ( markedLine == 3){
						button == BUT_UP ? setDischargeCurrent += 10 : setDischargeCurrent -= 10;
						if ( setDischargeCurrent < 10) setDischargeCurrent = 10;
						else if ( setDischargeCurrent > 300) setDischargeCurrent = 300;
					}
					if ( markedLine == 4){
						button == BUT_UP ? nCycles++ : nCycles--;
						if ( nCycles < 1) nCycles = 1;
						else if ( nCycles > 99) nCycles = 99;
					}					
				}					
			}			
			if ( curFrame > FRAME_INFO && curFrame < FRAME_MODE && !fEdit) {
				button == BUT_UP ? markedLine-- : markedLine++;
				if ( !markedLine) markedLine++;
				else if ( markedLine > 4) markedLine--;
			} 
			else if ( curFrame == FRAME_MODE) {
				Serial << F("BUT_UP_DOWN_FRAME_MODE") << "\n";
				button == BUT_UP ? markedLineMode-- : markedLineMode++;
				if ( !markedLineMode) markedLineMode++;
				else if ( markedLineMode > 6) markedLineMode--;
			}
			break;
	}
}

Пришла мысль, что проще рассеивать энергию при разряде на самом мосфете, а не на резистивной нагрузке, схема упростится, мосфет нужен пожирней ватт на 60.
Дисплей на 2.4 дюйма OLED пока в дороге, попробовать пока цветной  что ли прикрутить, для колоритности..

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Смотрим зарядку из- серии Вымпел, к примеру Вымпел 56

 

 

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

ua6em пишет:
Смотрим зарядку из- серии Вымпел..

Это который с перемешиванием электролита кипячением.. Гы - Ca/Ca особенно его "любят", это которые с плотной упаковкой и тонкими пакетами, аж до помутнения электролита (с чего бы?).
Был достойный прибор "Кулон 912" c wifi, криво сделанный, но подающийся доработке. Но увы снят с производства.
В общем, философски изъясняясь - пельмени, конечно, можно и магазинские трескать, но мне домашние больше по нраву.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

так штатный генератор авто их тоже не улучшает хронически не дозаряжая...
всё настроено на то, чтобы владелец через три года шёл и покупал новый аккумулятор...
а что подразумевает ваша конструкция - 10 лет безотказной работы аккумулятора?
PS столько у меня прослужил штатный и никаких усилий с моей стороны, воду раза три за это время долил

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

ua6em пишет:
так штатный генератор авто их тоже не улучшает хронически не дозаряжая...всё настроено на то, чтобы владелец через три года шёл и покупал новый аккумулятор...а что подразумевает ваша конструкция - 10 лет безотказной работы аккумулятора?
PS столько у меня прослужил штатный и никаких усилий с моей стороны, воду раза три за это время долил

Прям крик души, какой то. :)
Нормальный производитель, всегда решает целый ряд задач. Повышение потребительских свойств, в частности, одна из них. Качество корейских аккумуляторов, например, как у меня, показатель этого. 9 лет работы без обслуживания, это о многом говорит. Правда и стоит немало. И не надо забывать, что это всего лишь стартерный аккумулятор, не рассчитанный на постоянный малый разряд всякими свистоперделками, как то сигналками и прочим, да еще в условиях низких температур, при малых пробегах авто. 
Так что реанимация и обслуживании отслуживщих свое и новых ширпотребовских аккумуляторов, особенно отечественного производства, это вопрос сугубо личный, даже сказал бы "интимный" :O 

А теперь ближе к делу, схема упростилась и работает без нареканий, разрядный мосфет IRF3205 работает в качестве нагрузки, греется прилично, теплоотвод с вентилятором обязателен. Зарядная силовая часть требует при токах более 3А лишь легкого обдува..

SLKH
Offline
Зарегистрирован: 17.08.2015

Domosed пишет:

А теперь ближе к делу, схема упростилась и работает без нареканий, разрядный мосфет IRF3205 работает в качестве нагрузки, греется прилично, теплоотвод с вентилятором обязателен. Зарядная силовая часть требует при токах более 3А лишь легкого обдува..

Схема управления разрядным транзистором несколько нетрадиционная. Емкость затвора в качестве элемента ФНЧ - а почему бы и нет?

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

ядро от гайвера ставить с каким загрузчиком?

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

ua6em пишет:
ядро от гайвера ставить с каким загрузчиком?

Себе ставлю на дуины OptiBoot v8, USBasp-ом.
Ядро от alexgyver, не обязательно использовать, но с ним по памяти экономичней получается и по быстродействию:

ядро UNO:
Скетч использует 19592 байт (60%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 684 байт (33%) динамической памяти, оставляя 1364 байт для локальных переменных
GyverCore:
Скетч использует 19336 байт (59%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 678 байт (33%) динамической памяти, оставляя 1370 байт для локальных переменных

Но обнаружен косячок: при неподключенном аккумуляторе показания напряжения болтались около 0, закорачивал - 0, посмотрел осциллографом на затворах зарядного и разрядного мосфетов. А там импульсы, при отключенном ШИМ, импульсы с 9 и 10 пина, 80 ns 160 гц. Иногда проскакивают до 40 us. Причем от ядра не зависит. Значит или дуина или загрузчик или gyverPWM lib - надо разбираться. Хотя если честно на работу зарядного вроде не влияет, за исключением болтанки показаний сотых долей вольта и ампера, но осадочек остается. И возможен сквознячок через мосфеты. Благо дроссель при таких таймингах спасает.

Итог - техзадание написать функцию 10 битного шима, 16 кГц, на 9 и 10 пины .. Опять в даташит лезть :( брр..
Как в том анекдоте:
-На'силовать будешь??
- Неет.
- А придется..

 

SLKH
Offline
Зарегистрирован: 17.08.2015

Domosed пишет:

 

 

Итог - техзадание написать функцию 10 битного шима, 16 кГц, на 9 и 10 пины .. Опять в даташит лезть :( брр..

ЕвгенийП давно написал "ШИМ произвольного разрешения на UNO/Nano (328)" и выложил в режиме "Кому надо, пользуйтесь."

Где-то в дебрях форума это есть.

 

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

SLKH пишет:
ЕвгенийП давно написал "ШИМ произвольного разрешения на UNO/Nano (328)" ..

Благодарю за наводку.
Гугл легко справился с дебрями  - и выдал "ШИМ произвольного разрешения на UNO/Nano (328)" 
Вот оно
ЕвгенийП пишет:
При записи 0 (причём, когда там и так ноль был) иногда откуда-то высокий уровень проскакивает. Не часто и всплеск короткий - но непорядок. Ноль он должен и в Африке быть нулём. Сейчас пытаюсь отловить проблему и найти решение.

 

 

 

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

Да, так и сделаю  #include "FlexPWM.h"
Проблему с отсутствием защиты от переполюсовки аккумулятора - думаю решить так:

иного пути не вижу.

И еще: измерения и реагирование на изменения напряжения и тока происходят последовательно в основном цикле loop и занимает по времени 3..4 ms, но при выводе показаний, они происходят 4 раза в секунду, уже 60 ms. Что приводит к большему запаздыванию реакции управления ШИМ, как результат опять же болтанка напряжения и тока в сотых долях. Не комильфо..
Возникает необходимость в распараллеливании задач индикации и собственно измерений и управления.
Какие будут мысли у участников форума по этому поводу ( смена платформы не обсуждается) ?

 

ВН
Offline
Зарегистрирован: 25.02.2016

Жуть конечно, а не проект.

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

Задачка с переполюсовкой аккумулятора решена, аппаратно.

Красиво, самому нравится ;)

Защита от КЗ програмная, смотрим скетч..
Теперь дума о расспаралеливании процессов управления и индикации..

 

-NMi-
Offline
Зарегистрирован: 20.08.2018

Дык чо, таймер тебе в помощь. Делаешь событие, допустим через хх миллисекунд и считаешь ШИМ, ещё одно событие, ууу с периодичностью в одну секунду и выводишь на дисплей... Делофф то)))))

ВН
Offline
Зарегистрирован: 25.02.2016

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

А где озвучены параметры дросселя?

"на порошке" это очень вольно,  Например желто-белый сердечник это материал -26, а есть и -2 и -52 ну и промежуточных больше десятка.

Вообще на самодельный дроссель приводятся : размер сердечника, материал, число витков, параметры провода, контрольная индуктивность. Могут и какие-то другие данные приводиться, особенности намотки, например, но первые - обязательно.

 

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

ВН пишет:

А где озвучены параметры дросселя?

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

ВН
Offline
Зарегистрирован: 25.02.2016

ua6em пишет:
лучше приводить откуда энтот сердечник можно выдрать, к примеру из компьютерного блока питания

скорее "на худой конец". Комповые БП сейчас, как по схемам, так и по мощности бывают весьма разными, найти одноименный БП и выдрать из него дроссель ......тот еще мазохизм  

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Любите Авиту, неиссякаемый источник дросселей.  Тока добыть надо из руды древних БП за 100р.  Я беру нерабочие на 200-300Вт и раскурочиваю, они древние понадежней были. 

ВН
Offline
Зарегистрирован: 25.02.2016

На али колец на распыленке тоже в достатке  https://aliexpress.ru/item/32299616837.html

 Т106 -26 было вроде самым распространенным в старых БП

В тексте этой ссылки есть список материалов по распыленке, таблица потерь  от частоты для них, + таблица рекомендаций к применению https://aliexpress.ru/item/32296637542.html

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

ВН пишет:

ua6em пишет:
лучше приводить откуда энтот сердечник можно выдрать, к примеру из компьютерного блока питания

скорее "на худой конец". Комповые БП сейчас, как по схемам, так и по мощности бывают весьма разными, найти одноименный БП и выдрать из него дроссель ......тот еще мазохизм  

я про старые БП там ГЛАВНЫЙ дроссель был один )))

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

DetSimen пишет:

Любите Авиту, неиссякаемый источник дросселей.  Тока добыть надо из руды древних БП за 100р.  Я беру нерабочие на 200-300Вт и раскурочиваю, они древние понадежней были. 

там обычно и 5 вольтовый бп целый )))

PS колечко выходит дороже целого блока питания ...

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Да они 80% рабочие, тока дутыши поменять, при желании можно восстановить

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

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

Практика использования говорит об обратном. Повторяемость отличная, поскольку уже не один мосфет, шотки и дроссель, были испытаны в силовых цепях. Из имеющегося хлама подобрал, то что меньше греется. Но корпуса ТО-252 не очень практичны.
ВН пишет:
А где озвучены параметры дросселя? "на порошке" это очень вольно,  Например желто-белый сердечник это материал -26, а есть и -2 и -52 ну и промежуточных больше десятка.

Хороший материал по кольцам. У меня использован неизвестный дроссель на желто-белом кольце 22 мм. внешнего диаметра с 26 витками однож.провода D1.2mm. Материал кольца смесь 26, лучше 52. Соответственно греется провод дросселя, нужен литцендрат. Такие кольца используются как дросселя групповой стабилизации в блоках питания компьютеров.

Пройдусь по деталям:
Мосфеты - Uds>=25V   Vgs3..4V   Id>20A   Rds<20m (с материнских плат)
BJT - Uke>25V    Ik>0.3A  (из старых мониторов)
Диоды шотки  >10A  >20V (AT ATX БП)

По поводу распараллеливания процессов:
Некогда и лень, проще округлить показания до десятых долей вольта и ампера, шоб юзеры не волновались. :$
А вот меню надо переделывать, народ хочет шоб было красиво..

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

Охлаждение, просто и надежно, рекомендую.

Биполярный транзистор в этой схеме закономерно греется.
А mosfet, какой-никакой, но ключевой элемент. Самый простой вариант.
Терморезистор у меня 100 ком,остеклованный, капелькой. Но это не критично, как и выбор mosfet.
Как всегда смотрим на Rds, чем меньше тем лучше (с материнок, low level ;)

PS. Beta версия, дроссель на салатно-голубом кольце 22 витка двойным проводом d1.0mm

         ¯\_(ツ)_/¯

 

neid86@gmail.com
Offline
Зарегистрирован: 28.10.2014

А какая частота ШИМ заряда АКБ данной сие поделки?

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

neid86@gmail.com пишет:
А какая частота ШИМ заряда АКБ данной сие поделки?

16 ГГц  в полнолуние, на ущербной меньше, из-за резонанса Шумана..
Где то в скетче упоминается.

PWM_16KHZ_D10(pwmCharge);

PS. i make pause

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

Работа над зарядным продолжается. Внедрен код для работы с ШИМ от ЕвгенияП. Уменьшились колебания показаний тока зарядки.
Устройство скомпановано в единый блок - плата, разрядный мосфет на теплоотводе, вентилятор. 

ino ( мелкие исправления + автоопределение аккумуляторов Ni-Сd, Li-Ion, lead 6V и 12v, с автоматической установкой щадящих параметров заряда/разряда )

 

/**********************************************************************************
 * DomosedUnlimitedSoftware present. FREE, GNU, PNU licensing 2022(:)).
 **********************************************************************************
 * Заряд свинцовых 6V 12V и LiIon аккумуляторов  
 * !!! Если платформа не GyverCore закоментировать 	analogPrescaler( 64);
 * !!! НЕ ДОПУСКАТЬ ПЕРЕПОЛЮСОВКИ ПИТАНИЯ!!!   
 * !!! ПЕРЕД ЗАПУСКОМ ЗАРЯДА РЕКОМЕНДУЮ ПРОВЕРИТЬ ПАРАМЕТРЫ !!!   
 * !!! В скетче все наряжения и токи в сотых долях, т.е. U 1240 = 12.40V, I 150 = 1.5A
 * В доработку:
 *		связность экранов и пунктов меню
 * 		вывод в UART
 * 		расширить функциональность ( допрежимы + автоопределение АКБ 12V, 6V, LiIon)
 *		добавить поддержку щелочных аккумуляторов
 *		добавить светоиндикацию фаз работы светодиодами
 *		запись параметров в 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 pinLedWork 13
#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 MODE_CC_CHARGE			1
#define MODE_CV_CHARGE			2
#define MODE_CC_CHARGE_DISHARGE	3
#define MODE_CV_CHARGE_DISHARGE 4
#define MODE_DISHARGE 			5

uint8_t operatingMode = MODE_CC_CHARGE;
/***************** тип аккумулятора **********************/
#define BAT_NICD	1   // никель-кадмиевый
#define BAT_LIION	2   // литий-ион и тп
#define BAT_LEAD6V	3   // свинцовый 6V
#define BAT_LEAD12V	4   // свинцовый 12V

uint8_t	batteryType = 0;
/***************** фазы работы **********************/
#define STOP 		0   // стоп 
#define ACTIVATION  1   // активация аккумулятора
#define CHARGE      2   // заряд
#define CH_PAUSE    3   // пауза после заряда
#define DISCHARGE   4   // разряд
#define DSC_PAUSE   5   // пауза после разряда

uint8_t workPhase = STOP;
uint8_t	prevWorkPhase; 
/***************** фреймы экрана **********************/
#define FRAME_INFO 		0	// информационный 
#define FRAME_VOLTAGE	1	// меню установки напряжений
#define FRAME_CURRENT	2	// меню установки токов
#define FRAME_MODE		11	// меню выбора режима работы
#define FRAME_START		12	// старт/стоп

uint8_t curFrame = FRAME_INFO;
uint8_t prevCurFrame;		
uint8_t markedLine = 1;		// пункт меню
uint8_t markedLineMode = 1;	// пункт меню режимов

bool fEdit = false;			// флаг редактирования параметров
/******************** Настроечные значения **********************/
int16_t setActVoltage = 1200;       // напряжение окончания активации 12.00V
int16_t setMinVoltage = 1220;       // минимальное напряжение для разряда 12.50V
int16_t setMaxVoltage = 1420;       // максимальное напряжение для заряда 14.20V
int16_t setSaveVoltage = 1270;      // напряжение хранения 12.7V

int16_t setActChargeCurrent = 50;   // ток активационного заряда 0.5A
int16_t setChargeCurrent = 150;   	// ток заряда	1.5A
int16_t setCutChargeCurrent = 40;   // ток отсечки заряда CV 0,4A
int16_t setDischargeCurrent = 150;	// ток разряда	1.5A

uint8_t nCycles = 1;				// число циклов заряд/разряд
/******************** Действующие значения **********************/
int16_t workActChargeCurrent;   	// ток активационного заряда, рабочий
int16_t workChargeCurrent;   		// ток заряда, рабочий
int16_t workCutChargeCurrent;		// ток отсечки заряда CV, рабочий
int16_t workDischargeCurrent;		// ток разряда, рабочий

int16_t workVoltagePause;			// пауза, до падения напряжения
uint8_t cycleCounter;				// счетчик циклов зарядов/разрядов
/****************** Рабочие, текущие *******************/
uint16_t BatVoltage;				// текущее напряжение батареи
int16_t ChargeCurrent;			// текущий ток заряда			
int16_t DischargeCurrent;		// текущий ток разряда

float chargeEnergy;             // полученная энергия при заряде Ам.час
float dischargeEnergy;          // отданная энергия при разряде Ам.час

uint16_t pwmCharge = 0;			// ШИМ заряда
uint16_t pwmDischarge = 0;		// ШИМ разряда

uint32_t onTime = 0;			// время от включения прибора, секунды
uint32_t TimeWork = 0;			// время работы сеанса, секунды
uint32_t prevTime = 0;			// 
uint16_t timeHours;				// часы
uint16_t timeMins;				// минуты
uint16_t timeSecs;				// секунды

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( pinLedWork, 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);
}

void loop() 
{ 
	static unsigned long dt;			// время loop
	
	prevWorkPhase = workPhase;			// предыдущий режим
	procPresButtons();					// обработка по нажатию кнопок
	aplySettings( fEdit);				// применить изменение параметров
	getTimeWork();						// получить время работы
	getVoltageCurrent();				// получить напряжение батареи, токи заряда, разряда
	detectionBatteryType();				// определение типа батареи
	if ( BatVoltage < 50) 				// если напряжение аккумулятора < 0.5V 
		workPhase = STOP;				// останов
	energyCalculation();				// подсчет полученной, отданной энергии
	displayFrame( curFrame, markedLine);// вывод на дисплей
	
	/***************** отработка режимов *****************/
	switch ( workPhase) 
	{
		case STOP:				// останов
			ChargingBattery( 0, 0, 0);
			DischargingBattery( 0, 0);
			digitalWrite(pinLedWork, 0);
			makePause( 0, 0);
			break;
		case ACTIVATION:		// активация батареи
			if ( BatVoltage < setActVoltage) { 
				if ( ChargingBattery( workActChargeCurrent, 0, setActVoltage)) 
					workPhase = CHARGE; 
			} 
			else workPhase = CHARGE;
			break;
		case CHARGE:			// заряд батареи
			if ( workChargeCurrent == 0) workPhase = DISCHARGE; // заряд не нужен
			else if ( ChargingBattery( workChargeCurrent, workCutChargeCurrent, setMaxVoltage))
				workPhase = CH_PAUSE; 
			break;
		case CH_PAUSE:			// пауза
			if ( makePause( 10, 0)) 
				workPhase = DISCHARGE;
			break;
		case DISCHARGE:			// разряд постоянным током
			if ( DischargingBattery( workDischargeCurrent, setMinVoltage)){
				if ( workDischargeCurrent) cycleCounter++;
				if ( cycleCounter == nCycles) {
					workPhase = STOP;
					digitalWrite(pinLedWork, 0);
				}
				else workPhase = DSC_PAUSE;
			}
			break;
		case DSC_PAUSE:			// пауза
			if ( makePause( 10, workVoltagePause)) workPhase = ACTIVATION;
			break;
	}
	dt = millis() - dt;			// длительность loop
	//Serial << F("dT ") << dt << '\n';
	//if ( dt > 50)               // вывод при обновлении дисплея, когда dt>50ms
	//	Serial << F("U=") << BatVoltage << F(" I=") << ChargeCurrent << '/' << DischargeCurrent 
	//			<< F(" PWM=") << pwmCharge << '/' << pwmDischarge << F(" dT") << dt << F(" En=") << chargeEnergy << '/' << dischargeEnergy << '\n';
	dt = millis();
}
/*****************************************************
 * Автоматическое определение типа аккумулятора
 * LiIon или 12V свинцовая, и установка параметров
 * работы по умолчанию.
 * void detectionBatteryType()
*****************************************************/
void detectionBatteryType()
{
	if ( workPhase != STOP) return;
	else if ( BatVoltage > 50 && BatVoltage < 160 && ( batteryType != BAT_NICD)) {
			batteryType = BAT_NICD;
			setActVoltage = 100;       // напряжение окончания активации 1V
			setMinVoltage = 120;       // минимальное напряжение для разряда 1.20V
			setMaxVoltage = 150;       // максимальное напряжение для заряда 1.50V
			setSaveVoltage = 140;      // напряжение хранения 1.3V
			setActChargeCurrent = 10;   // ток активационного заряда 0.1A
			setChargeCurrent = 30;   	// ток заряда	0.3A
			setCutChargeCurrent = 10;   	// ток отсечки заряда CV 0,1A
			setDischargeCurrent = 50;	// ток разряда	0.5A			
		}
		else if ( BatVoltage > 200 && BatVoltage < 430 && ( batteryType != BAT_LIION)) {
			batteryType = BAT_LIION;
			setActVoltage = 290;       // напряжение окончания активации 2.90V
			setMinVoltage = 320;       // минимальное напряжение для разряда 3.20V
			setMaxVoltage = 420;       // максимальное напряжение для заряда 4.20V
			setSaveVoltage = 380;      // напряжение хранения 3.8V
			setActChargeCurrent = 10;   // ток активационного заряда 0.1A
			setChargeCurrent = 30;   	// ток заряда	0.3A
			setCutChargeCurrent = 10;   	// ток отсечки заряда CV 0,1A
			setDischargeCurrent = 50;	// ток разряда	0.5A			
		}
		else if ( BatVoltage > 450 && BatVoltage < 800 && ( batteryType != BAT_LEAD6V)){
			batteryType = BAT_LEAD6V;
			setActVoltage = 600;       // напряжение окончания активации6V
			setMinVoltage = 610;       // минимальное напряжение для разряда 6.10V
			setMaxVoltage = 710;       // максимальное напряжение для заряда 7.10V
			setSaveVoltage = 630;      // напряжение хранения 6.3V
			setActChargeCurrent = 50;   // ток активационного заряда 0.5A
			setChargeCurrent = 150;   	// ток заряда	1.5A
			setCutChargeCurrent = 40;   // ток отсечки заряда CV 0,4A
			setDischargeCurrent = 150;	// ток разряда	1.5A
			}
		else if ( BatVoltage > 800 && BatVoltage < 1500 && ( batteryType != BAT_LEAD12V)){
			batteryType = BAT_LEAD12V;
			setActVoltage = 1200;       // напряжение окончания активации 12.00V
			setMinVoltage = 1220;       // минимальное напряжение для разряда 12.20V
			setMaxVoltage = 1420;       // максимальное напряжение для заряда 14.20V
			setSaveVoltage = 1270;      // напряжение хранения 12.7V
			setActChargeCurrent = 50;   // ток активационного заряда 0.5A
			setChargeCurrent = 150;   	// ток заряда	1.5A
			setCutChargeCurrent = 40;   // ток отсечки заряда CV 0,4A
			setDischargeCurrent = 150;	// ток разряда	1.5A
			}
}
/*****************************************************
 * Сохранить измененные параметры настроек
 * void aplySettings( bool edit)
*****************************************************/
void aplySettings( bool edit)
	{
		if ( edit) {
			workActChargeCurrent = setActChargeCurrent;	// ток активации 
			workChargeCurrent = setChargeCurrent;		// ток заряда
			workCutChargeCurrent = setCutChargeCurrent;	// ток отсечки заряда CV
			workDischargeCurrent = setDischargeCurrent;	// ток разряда
			workVoltagePause = setSaveVoltage;			// напряжение хранения	
		}
	}	
/*****************************************************
 * Подсчет полученной, отданной энергии в ампер часах
 * energyCalculation()
*****************************************************/
void energyCalculation(){
  if ( onTime > prevTime) { 
    prevTime = onTime;
    //chargeEnergy += ( (uint32_t)ChargeCurrent << 16) / 3600;
    //dischargeEnergy += ( (uint32_t)DischargeCurrent << 16) / 3600;
    chargeEnergy += ((float)ChargeCurrent) / 360000.0;
    dischargeEnergy += ((float)DischargeCurrent) / 360000.0;	
    }
}
/*****************************************************
 * сделать паузу по времени или падению напряжения
 * bool makeРause(uint32_t tp, int16_t voltage)
 * 		tp - время паузы в секундах, если 
 *		voltage - не равно 0, по падению напряжения
 *		ниже voltage * 
 * 		return false - пауза не окончена  
 * 		return true  - пауза закончилась  
 ****************************************************/
 bool makePause( uint32_t tpause, int16_t voltage)
 {
    static uint32_t tpStart = 0;
	if ( voltage)
		if ( BatVoltage >= voltage) return false;
		else return true;
    if ( !tpause) { 
      tpStart = 0;
      return true;
    }
    if ( !tpStart) tpStart = TimeWork;
    if (( TimeWork - tpStart) >= tpause) 
    {
      tpStart = 0;
      return true;
    }
    else return false;  
 }
/*************************************************************************************
 * Заряд аккумулятора СС - постоянным током до достижения voltage на аккумуляторе.
 * Если mincurrent не равен 0, то после достижения voltage, 
 * заряд CV - падающим током до mincurrent
 * 
 * bool ChargingBattery( uint16_t constcurrent, uint16_t mincurrent, uint16_t maxvoltage) 
 *				constcurrent - ток заряда Амперы
 *        mincurrent - min ток поддержания заряда
 *        voltage - предел заряда по напряжению
 * return false - заряд продолжается
 *        true  - по окончанию заряда
 ****************************************************/
bool ChargingBattery( uint16_t constcurrent, uint16_t mincurrent, uint16_t maxvoltage)
{
	uint16_t dPwm;	 			// дельта приращения pwm
	static bool fCV = false;	// флаг CV - поддержания напряжения
 
	/* если constcurrent=0, или достигнуто напряжение, при 0 mincurrent - останов заряда */
	if ( constcurrent == 0 || (( BatVoltage > maxvoltage) && ( mincurrent == 0))) {
endcharge:    		
		pwmCharge =	0;
		fCV = false;
		digitalWrite10( pwmCharge);		// ШИМ  = 0
		return true; 
		}
   
	if ( fCV || ( batteryType != BAT_LEAD12V)) dPwm = 1;					// если CV поддержание или LiIon
	  else if ( pwmCharge < 200) dPwm = 5;
		  else if ( pwmCharge < 500) dPwm = 2;
			  else if ( pwmCharge < 700) dPwm = 1;
				  else if ( pwmCharge < 900) dPwm = 1;
					  else if ( pwmCharge < 1000) dPwm = 1;
						  else dPwm = 1;
  
  if ( BatVoltage >= maxvoltage) 
  {
    fCV = true;
    if ( ChargeCurrent > mincurrent)
    {
      if ( pwmCharge > dPwm) {
        pwmCharge -= dPwm;
        goto retcharge;
      }
    } else goto endcharge;
  }
  if (( BatVoltage < (maxvoltage)) && fCV)
    pwmCharge += dPwm;
      
  if ( !fCV) {                	 
	  if ( ChargeCurrent < constcurrent) {
		  if ( pwmCharge < 1023) pwmCharge += dPwm;
	  } 
	  else 
	  {
		  if ( ChargeCurrent > (constcurrent))
		  	if ( pwmCharge > dPwm) pwmCharge -= dPwm;
	  }
  }
retcharge: 
	pwmWrite10( pwmCharge);
  return false;
}
/*****************************************************
 * Разряд аккумулятора постоянным током current
 * bool DischargingBattery( uint16_t current, uint16_t voltage) 
 *				current - ток разряда Амперы
 *        voltage - предел разряда по напряжению
 * return false  - если напряжение не занижено
 *        true - по окончанию разряда
*****************************************************/
bool DischargingBattery( uint16_t current, uint16_t voltage)
{
	uint16_t dPwm;	 // дельта приращения pwm
	
	if ( current == 0 || BatVoltage < voltage) {		// если ток=0, или занижено напряжение - останов заряда
		pwmDischarge =	0;
		digitalWrite9( pwmDischarge);		// ШИМ  = 0
		return true; 
		}
		
	if ( batteryType < BAT_LEAD6V) dPwm = 1;	
	if ( pwmDischarge < 200) dPwm = 10;
		else if ( pwmDischarge < 500) dPwm = 5;
			else if ( pwmDischarge < 700) dPwm = 2;
				else if ( pwmDischarge < 900) dPwm = 2;
					else if ( pwmDischarge < 1000) dPwm = 1;
						else dPwm = 1;	 
	if ( DischargeCurrent < current) {
		if ( pwmDischarge < 1023) pwmDischarge += dPwm;
	} else {
		if ( DischargeCurrent > (current + 2))
			if ( pwmDischarge >dPwm) pwmDischarge -= dPwm;
	}
	pwmWrite9( pwmDischarge);
  return	 false;
}
/*****************************************************
 * getVoltageCurrent() - считать напряжение и токи
*****************************************************/
void getVoltageCurrent(){
	uint16_t buf;
	{ // напряжение на батарее 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_Voltage);
		}
		buf = buf / 16;
		BatVoltage = uint16_t(buf) * 25 /10;
	}	
	{ // ток заряда 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_ChargeCurrent);
		}
		buf = buf / 16;
		ChargeCurrent = buf * 3;  // шунт - 3 резистора 0.1
	}
	{ // ток разряда 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_DischargeCurrent);
		}
		buf = buf / 16;
		DischargeCurrent = buf * 2;  // шунт - 2 резистора 0.1 в параллель
	}
	if ( BatVoltage > 50 && BatVoltage < 1500) 
		BatVoltage += (1500-BatVoltage)/200; 	// поправка для низких напряжений
	BatVoltage += ((-ChargeCurrent)/30 + DischargeCurrent/20); // 3 и 2 число резисторов по 0.1 ома
}
/*****************************************************
 * getTimeWork() - время работы сеанса
*****************************************************/
void getTimeWork()
{
	static bool fPrevStart = false;
	static uint32_t timeStart = 0;						// время старта работы
	onTime = millis() / 1000ul;							// время от включения прибора
	if ( (workPhase != STOP) && !fPrevStart) {	 
		fPrevStart = true;
		timeStart = millis();
	}
	if ( (workPhase != STOP)) {						// если не останов
		TimeWork = (millis() - timeStart) / 1000ul;		// полное количество секунд работы	
	} else {
		TimeWork = 0;
		timeStart = 0;
		fPrevStart = false; 
	}	
	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) >= 250) 		// обновление экрана 4 раза в секунду
		tDisplay = ttt;
	else return;
	
	display.setTextSize( 2);
	display.clearDisplay();
	display.setCursor(0,0);
	if ( frame == FRAME_INFO) {
		int16_t current = ( ChargeCurrent > DischargeCurrent ? ChargeCurrent : -DischargeCurrent);
		sprintf(bufStr, "Vb %02d.%02dv", BatVoltage/100, BatVoltage%100);
		display.println(bufStr);
		sprintf(bufStr, "Ic %02d.%02da", current/100, abs(current)%100);
		display.println(bufStr);
		uint32_t dt = ( onTime + 1)%4;
		if ( dt == 1 || dt == 2) { 
			sprintf(bufStr, "T%3d:%02d:%02d", timeHours, timeMins, timeSecs);	
			display.print(bufStr);
			sprintf(bufStr, "Pwm %04d", pwmCharge > pwmDischarge ? pwmCharge : -pwmDischarge);
			display.println(bufStr);
		} 
		else if ( dt == 3) {		
			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);
		} else {
				display.print( F("----------"));
				if ( workPhase == STOP) display.print( F(" STOP")); 
				else if ( workPhase == ACTIVATION) display.print( F("ACTIVATION")); 
				else if ( workPhase == CHARGE) display.print( F(" CHARGE")); 
				else if ( workPhase == CH_PAUSE || workPhase == DSC_PAUSE ) display.print( F(" PAUSE"));
				else if ( workPhase == DISCHARGE) display.print( F(" DISCHARGE"));
			}
	} 
	else if ( frame == FRAME_START) {
		sprintf(bufStr, "Vb %02d.%02dv", BatVoltage/100, BatVoltage%100);
		display.println(bufStr);		
		display.setTextSize( 3);
		display.setCursor(0,24);
		if ( workPhase != STOP) display.println( F(" STOP?"));
		else display.println( F(" START?"));
	} 
	else if ( frame == FRAME_MODE) {
		if ( markedLineMode < 5) {
			display.print( F(" CC charge"));
			display.print( F(" CCCV chrg"));
			display.print( F(" CC ch/dis"));
			display.print( F(" CCCV ch/d"));
			display.setCursor( 0, ( markedLineMode -1 ) *16);			
		} else if ( markedLineMode < 9) {
			display.print( F(" Discharge"));
			display.println( F(" ..."));
			display.print( F(" ..."));
			display.print( F(" ..."));
			display.setCursor( 0, ( markedLineMode -5 ) *16);
		}
		display.print( F("> "));		
	} 
	else if ( frame == FRAME_VOLTAGE) {
		display.print( F(" Uact")); display.print( (float)setActVoltage / 100.0);
		if ( setActVoltage < 1000) display.print( F( "\n"));
		display.print( F(" Umin")); display.print( (float)setMinVoltage / 100.0);
		if ( setMinVoltage < 1000) display.print( F( "\n"));		
		display.print( F(" Umax")); display.print( (float)setMaxVoltage / 100.0);
		if ( setMaxVoltage < 1000) display.print( F( "\n"));
		display.print( F(" Usav")); display.print( (float)setSaveVoltage / 100.0);
		if ( setSaveVoltage < 1000) display.print( F( "\n"));
		display.setCursor( 0, ( marked -1 ) *16);
		if ( fEdit) display.print( F("* "));
		else display.print( F("> "));
	} 
	else if ( frame == FRAME_CURRENT) {
		display.print( F(" Ichg ")); display.print( (float)setChargeCurrent / 100.0);
		display.print( F(" Icut ")); display.print( (float)setCutChargeCurrent / 100.0);
		display.print( F(" Idis ")); display.print( (float)setDischargeCurrent / 100.0);
		display.print( F(" cycles ")); display.print( nCycles);
		display.setCursor( 0, ( marked -1 ) *16);
		if ( fEdit) display.print( F("* "));
		else display.print( F("> "));
	} 
	display.display();
}
/****************************************************
 * uint8_t getButtonState() опрос кнопок
*****************************************************/
uint8_t getButtonState()
{	
	static uint32_t pressTime = 0;         // время нажатия кнопки
	static uint8_t button;
	uint8_t state = (~PIND & 0b1111100) >> 2;
	if ( state && !pressTime) 
	{ 
		pressTime = millis();
		button = state;
	}
	if ( !state && pressTime) 
	{
		if ( ( millis() - pressTime) > 150)
		{
			pressTime = 0;
			return button;
		}
	}
	return 0;
}
/*****************************************************
 * Обработка по нажатию кнопок
 * void procPresButtons()
*****************************************************/
void procPresButtons()
{
	uint8_t button = getButtonState();
	
	switch ( button)
	{
		case BUT_OK:	// переход на выбор режима работы с последующим стартом или останов
			if ( curFrame == FRAME_INFO) {
				if ( workPhase != STOP) curFrame = FRAME_START;
				else curFrame = FRAME_MODE;				
			}
			else if ( curFrame == FRAME_START) {
				if ( workPhase != STOP) 
				{	
					workPhase = STOP;		
					digitalWrite(pinLedWork, 0);
				} else {
					cycleCounter = 0;		// по старту сброс накопленных показаний
					chargeEnergy = 0;
					dischargeEnergy = 0;
					workPhase = ACTIVATION;
					digitalWrite(pinLedWork, 1); 
				}
				curFrame = FRAME_INFO;
			}
			else if ( curFrame == FRAME_MODE) {	// выбор режима перед стартом
				
				if ( markedLineMode == 1) {	// CC ch save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = 0;
					workDischargeCurrent = 0;
					workVoltagePause = setSaveVoltage;
				}
				if ( markedLineMode == 2) { // CV ch save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = setCutChargeCurrent;
					workDischargeCurrent = 0;
					workVoltagePause = setSaveVoltage;					
				}
				if ( markedLineMode == 3) { // CC ch/dis no save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = 0;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 4) { // CV ch/dis no save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = setCutChargeCurrent;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 5) { // discharge 
					workActChargeCurrent = 0;
					workChargeCurrent = 0;
					workCutChargeCurrent = 0;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				curFrame = FRAME_START;
			}
			else if ( curFrame == FRAME_VOLTAGE || curFrame == FRAME_CURRENT)	
				fEdit = !fEdit;
			break;

		case BUT_RIGHT:
			if ( workPhase == STOP && !fEdit) {
				markedLine = 1;
				if ( curFrame == FRAME_CURRENT) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_MODE) curFrame = FRAME_INFO;
					else curFrame++;
			} 
			else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
			break;
		case BUT_LEFT:
			if ( workPhase == STOP && !fEdit) {
				markedLine = 1;
				if ( curFrame == FRAME_INFO) curFrame = FRAME_CURRENT;
				else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_MODE) curFrame = FRAME_INFO;
					else curFrame--; 
			} 
			else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
			break;

		case BUT_UP:
		case BUT_DOWN:
			//Serial << F("BUT_UP_DOUN") << "\n";
			if ( fEdit) {
				if ( curFrame == FRAME_VOLTAGE){
					if ( markedLine == 1){
						button == BUT_UP ? setActVoltage += 10 : setActVoltage -= 10;
						if ( setActVoltage < 290) setActVoltage = 290;
						else if ( setActVoltage > 1200) setActVoltage = 1200;
					}
					if ( markedLine == 2){
						button == BUT_UP ? setMinVoltage += 10 : setMinVoltage -= 10;
						if ( setMinVoltage < 320) setMinVoltage = 320;
						else if ( setMinVoltage > 1220) setMinVoltage = 1220;
					}
					if ( markedLine == 3){
						button == BUT_UP ? setMaxVoltage += 10 : setMaxVoltage -= 10;
						if ( setMaxVoltage < 390) setMaxVoltage = 390;
						else if ( setMaxVoltage > 1620) setMaxVoltage = 1620;
					}
					if ( markedLine == 4){
						button == BUT_UP ? setSaveVoltage += 10 : setSaveVoltage -= 10;
						if ( setSaveVoltage < 370) setSaveVoltage = 370;
						else if ( setSaveVoltage > 1280) setSaveVoltage = 1280;
					}					
				}
				if ( curFrame == FRAME_CURRENT){
					if ( markedLine == 1){
						button == BUT_UP ? setChargeCurrent += 10 : setChargeCurrent -= 10;
						if ( setChargeCurrent < 10) setChargeCurrent = 10;
						else if ( setChargeCurrent > 500) setChargeCurrent = 500;
					}
					if ( markedLine == 2){
						button == BUT_UP ? setCutChargeCurrent += 10 : setCutChargeCurrent -= 10;
						if ( setCutChargeCurrent < 10) setCutChargeCurrent = 10;
						else if ( setCutChargeCurrent > 100) setCutChargeCurrent = 100;
					}
					if ( markedLine == 3){
						button == BUT_UP ? setDischargeCurrent += 10 : setDischargeCurrent -= 10;
						if ( setDischargeCurrent < 10) setDischargeCurrent = 10;
						else if ( setDischargeCurrent > 500) setDischargeCurrent = 500;
					}
					if ( markedLine == 4){
						button == BUT_UP ? nCycles++ : nCycles--;
						if ( nCycles < 1) nCycles = 1;
						else if ( nCycles > 99) nCycles = 99;
					}					
				}					
			}			
			if ( curFrame > FRAME_INFO && curFrame < FRAME_MODE && !fEdit) {
				button == BUT_UP ? markedLine-- : markedLine++;
				if ( !markedLine) markedLine++;
				else if ( markedLine > 4) markedLine--;
			} 
			else if ( curFrame == FRAME_MODE) {
				//Serial << F("BUT_UP_DOWN_FRAME_MODE") << "\n";
				button == BUT_UP ? markedLineMode-- : markedLineMode++;
				if ( !markedLineMode) markedLineMode++;
				else if ( markedLineMode > 5) markedLineMode--;
			}
			break;
	}
}

FlexPWM.h :
 

/**********************************************************************************************************
 * ШИМ произвольного разрешения на UNO/Nano (328) пины 9 и 10 ( ЕвгенийП )
 * https://arduino.ru/forum/programmirovanie/shim-proizvolnogo-razresheniya-na-unonano-328
 * https://arduino.ru/forum/programmirovanie/shim-proizvolnogo-razresheniya-na-unonano-328#comment-281581
 * Таблица соответствия частоты ШИМ и разрядности
 * Бит	Макс	Частота контроллера (МГц)
 * 				1			2			4			8			16			20
 * 2	3		250 000,0	500 000,0	1 000 000,0	2 000 000,0	4 000 000,0	5 000 000,0
 * 3	7		125 000,0	250 000,0	500 000,0	1 000 000,0	2 000 000,0	2 500 000,0
 * 4	15		62 500,0	125 000,0	250 000,0	500 000,0	1 000 000,0	1 250 000,0
 * 5	31		31 250,0	62 500,0	125 000,0	250 000,0	500 000,0	625 000,0
 * 6	63		15 625,0	31 250,0	62 500,0	125 000,0	250 000,0	312 500,0
 * 7	127		7 812,5		15 625,0	31 250,0	62 500,0	125 000,0	156 250,0
 * 8	255		3 906,3		7 812,5		15 625,0	31 250,0	62 500,0	78 125,0
 * 9	511		1 953,1		3 906,3		7 812,5		15 625,0	31 250,0	39 062,5
 * 10	1 023	976,6		1 953,1		3 906,3		7 812,5		15 625,0	19 531,3
 * 11	2 047	488,3		976,6		1 953,1		3 906,3		7 812,5		9 765,6
 * 12	4 095	244,1		488,3		976,6		1 953,1		3 906,3		4 882,8
 * 13	8 191	122,1		244,1		488,3		976,6		1 953,1		2 441,4
 * 14	16 383	61,0		122,1		244,1		488,3		976,6		1 220,7
 * 15	32 767	30,5		61,0		122,1		244,1		488,3		610,4
 * 16	65 535	15,3		30,5		61,0		122,1		244,1		305,2
 * Пример:
 * void setup(void)	{ pwmInit(13);}
 * void loop(void)	{ digitalWrite10(LOW); pwmWrite10(duty); digitalWrite10(HIGH); }
**********************************************************************************************************/
#ifndef	FlexPWM_h
#define	FlexPWM_h
#define __bit(b) (1u << (b))
/*****************************************************
 * Инициализация ШИМ на 9, 10 пине 
 * указанного разрешения, 10 бит по умолчанию.
 *****************************************************/
inline void pwmInit(const int8_t bitRate = 10) {
	noInterrupts();
	TCCR1A = __bit(WGM11);
	TCCR1B = __bit(WGM13) | __bit(WGM12) | __bit(CS10);
	TCCR1C = TCNT1 = OCR1A = OCR1B = TIMSK1 = TIFR1 = 0;
	ICR1 = (1u << bitRate) - 1u;
	interrupts();
}
/*****************************************************
 * Вывод ШИМ на 9 пине указанного значения
 *****************************************************/
inline void pwmWrite9(const uint16_t v) { 
	DDRB |= __bit(PB1);
	TCCR1A |= __bit(COM1A1);
	OCR1A = v;
}
/*****************************************************
 * Вывод на 9 пин  LOW или HIGH
 *****************************************************/
inline void digitalWrite9(const uint8_t v) {
	DDRB |= __bit(PB1);
	TCCR1A &= ~__bit(COM1A1); 
	if (v) PORTB |= __bit(PB1); else PORTB &= ~__bit(PB1);
}
/*****************************************************
* Вывод ШИМ на 10 пине указанного значения
 *****************************************************/
inline void pwmWrite10(const uint16_t v) {
	DDRB |= __bit(PB2);
	TCCR1A |= __bit(COM1B1);
	OCR1B = v;
}
/*****************************************************
 * Вывод на 10 пин  LOW или HIGH
 *****************************************************/
inline void digitalWrite10(const uint8_t v) {
	DDRB |= __bit(PB2);
	TCCR1A &= ~__bit(COM1B1);
	if (v) PORTB |= __bit(PB2); else PORTB &= ~__bit(PB2);
}
#endif	// FlexPWM_h

PS. Протестировано с вышеупомянутыми типами аккумуляторов, за исключением свинцовых 6V, нет в наличии.

-NMi-
Offline
Зарегистрирован: 20.08.2018

А уотт теперь отдай его на тест в любой гараж/сервис, где мужички побрутальнее/деревяннее и засеки время, пока его ушатают. Если выдержит хотя бы месяц --- поставлю жирную тройку!!!

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

-NMi- пишет:
А уотт теперь отдай его на тест в любой гараж/сервис, где мужички побрутальнее/деревяннее

Поскольку изначально в прошивке предусмотрен автоматический режим, то для превращения в "брутально-деревянный" аппарат, достаточно изменить "пару строк" кода, убрать диплей, светодиоды, все кнопки и вуаля - подцепил акк и иди кури. При переполюсовке и козе, устройство просто не запуститься.

Теперь по делу..
Очередная коррекция кода, выправлена нелинейность преобразования АЦП для низких значений напряжения,скорректированы параметры для Ni-Cd аккумуляторов.
 

/**********************************************************************************
 * DomosedUnlimitedSoftware present. FREE, GNU, PNU licensing 2022(:)).
 **********************************************************************************
 * Заряд свинцовых 6V 12V, LiIon и щелочных аккумуляторов  
 * !!! Если платформа не GyverCore закоментировать 	analogPrescaler( 64);
 * !!! НЕ ДОПУСКАТЬ ПЕРЕПОЛЮСОВКИ ПИТАНИЯ!!!   
 * !!! ПЕРЕД ЗАПУСКОМ ЗАРЯДА РЕКОМЕНДУЮ ПРОВЕРИТЬ ПАРАМЕТРЫ !!!   
 * !!! В скетче все наряжения и токи в сотых долях, т.е. U 1240 = 12.40V, I 150 = 1.5A
 * В доработку:
 *		связность экранов и пунктов меню
 * 		вывод в UART
 * 		расширить функциональность
 *		добавить светоиндикацию фаз работы светодиодами
 *		запись параметров в 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 pinLedWork 13
#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 MODE_CC_CHARGE			1
#define MODE_CV_CHARGE			2
#define MODE_CC_CHARGE_DISHARGE	3
#define MODE_CV_CHARGE_DISHARGE 4
#define MODE_DISHARGE 			5

uint8_t operatingMode = MODE_CC_CHARGE;
/***************** тип аккумулятора **********************/
#define BAT_NICD	1   // никель-кадмиевый
#define BAT_LIION	2   // литий-ион и тп
#define BAT_LEAD6V	3   // свинцовый 6V
#define BAT_LEAD12V	4   // свинцовый 12V

uint8_t	batteryType = 0;
/***************** фазы работы **********************/
#define STOP 		0   // стоп 
#define ACTIVATION  1   // активация аккумулятора
#define CHARGE      2   // заряд
#define CH_PAUSE    3   // пауза после заряда
#define DISCHARGE   4   // разряд
#define DSC_PAUSE   5   // пауза после разряда

uint8_t workPhase = STOP;
uint8_t	prevWorkPhase; 
/***************** фреймы экрана **********************/
#define FRAME_INFO 		0	// информационный 
#define FRAME_VOLTAGE	1	// меню установки напряжений
#define FRAME_CURRENT	2	// меню установки токов
#define FRAME_MODE		11	// меню выбора режима работы
#define FRAME_START		12	// старт/стоп

uint8_t curFrame = FRAME_INFO;
uint8_t prevCurFrame;		
uint8_t markedLine = 1;		// пункт меню
uint8_t markedLineMode = 1;	// пункт меню режимов

bool fEdit = false;			// флаг редактирования параметров
/******************** Настроечные значения **********************/
int16_t setActVoltage = 1200;       // напряжение окончания активации 12.00V
int16_t setMinVoltage = 1220;       // минимальное напряжение для разряда 12.50V
int16_t setMaxVoltage = 1420;       // максимальное напряжение для заряда 14.20V
int16_t setSaveVoltage = 1270;      // напряжение хранения 12.7V

int16_t setActChargeCurrent = 50;   // ток активационного заряда 0.5A
int16_t setChargeCurrent = 150;   	// ток заряда	1.5A
int16_t setCutChargeCurrent = 40;   // ток отсечки заряда CV 0,4A
int16_t setDischargeCurrent = 150;	// ток разряда	1.5A

uint8_t nCycles = 1;				// число циклов заряд/разряд
/******************** Действующие значения **********************/
int16_t workActChargeCurrent;   	// ток активационного заряда, рабочий
int16_t workChargeCurrent;   		// ток заряда, рабочий
int16_t workCutChargeCurrent;		// ток отсечки заряда CV, рабочий
int16_t workDischargeCurrent;		// ток разряда, рабочий

int16_t workVoltagePause;			// пауза, до падения напряжения
uint8_t cycleCounter;				// счетчик циклов зарядов/разрядов
/****************** Рабочие, текущие *******************/
uint16_t BatVoltage;				// текущее напряжение батареи
int16_t ChargeCurrent;			// текущий ток заряда			
int16_t DischargeCurrent;		// текущий ток разряда

float chargeEnergy;             // полученная энергия при заряде Ам.час
float dischargeEnergy;          // отданная энергия при разряде Ам.час

uint16_t pwmCharge = 0;			// ШИМ заряда
uint16_t pwmDischarge = 0;		// ШИМ разряда

uint32_t onTime = 0;			// время от включения прибора, секунды
uint32_t TimeWork = 0;			// время работы сеанса, секунды
uint32_t prevTime = 0;			// 
uint16_t timeHours;				// часы
uint16_t timeMins;				// минуты
uint16_t timeSecs;				// секунды

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( pinLedWork, 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);
}

void loop() 
{ 
	static unsigned long dt;			// время loop
	
	prevWorkPhase = workPhase;			// предыдущий режим
	procPresButtons();					// обработка по нажатию кнопок
	aplySettings( fEdit);				// применить изменение параметров
	getTimeWork();						// получить время работы
	getVoltageCurrent();				// получить напряжение батареи, токи заряда, разряда
	detectionBatteryType();				// определение типа батареи
	if ( BatVoltage < 50) 				// если напряжение аккумулятора < 0.5V 
		workPhase = STOP;				// останов
	energyCalculation();				// подсчет полученной, отданной энергии
	displayFrame( curFrame, markedLine);// вывод на дисплей
	
	/***************** отработка режимов *****************/
	switch ( workPhase) 
	{
		case STOP:				// останов
			ChargingBattery( 0, 0, 0);
			DischargingBattery( 0, 0);
			digitalWrite(pinLedWork, 0);
			makePause( 0, 0);
			break;
		case ACTIVATION:		// активация батареи
			if ( BatVoltage < setActVoltage) { 
				if ( ChargingBattery( workActChargeCurrent, 0, setActVoltage)) 
					workPhase = CHARGE; 
			} 
			else workPhase = CHARGE;
			break;
		case CHARGE:			// заряд батареи
			if ( workChargeCurrent == 0) workPhase = DISCHARGE; // заряд не нужен
			else if ( ChargingBattery( workChargeCurrent, workCutChargeCurrent, setMaxVoltage))
				workPhase = CH_PAUSE; 
			break;
		case CH_PAUSE:			// пауза
			if ( makePause( 10, 0)) 
				workPhase = DISCHARGE;
			break;
		case DISCHARGE:			// разряд постоянным током
			if ( DischargingBattery( workDischargeCurrent, setMinVoltage)){
				if ( workDischargeCurrent) cycleCounter++;
				if ( cycleCounter == nCycles) {
					workPhase = STOP;
					digitalWrite(pinLedWork, 0);
				}
				else workPhase = DSC_PAUSE;
			}
			break;
		case DSC_PAUSE:			// пауза
			if ( makePause( 10, workVoltagePause)) workPhase = ACTIVATION;
			break;
	}
	dt = millis() - dt;			// длительность loop
	//Serial << F("dT ") << dt << '\n';
	//if ( dt > 50)               // вывод при обновлении дисплея, когда dt>50ms
	//	Serial << F("U=") << BatVoltage << F(" I=") << ChargeCurrent << '/' << DischargeCurrent 
	//			<< F(" PWM=") << pwmCharge << '/' << pwmDischarge << F(" dT") << dt << F(" En=") << chargeEnergy << '/' << dischargeEnergy << '\n';
	dt = millis();
}
/*****************************************************
 * Автоматическое определение типа аккумулятора
 * и установка параметров работы
 * void detectionBatteryType()
*****************************************************/
void detectionBatteryType()
{
	if ( workPhase != STOP) return;
	else if ( BatVoltage > 50 && BatVoltage < 160 && ( batteryType != BAT_NICD)) {
			batteryType = BAT_NICD;
			setActVoltage = 100;       // напряжение окончания активации 1V
			setMinVoltage = 110;       // минимальное напряжение для разряда 1.20V
			setMaxVoltage = 150;       // максимальное напряжение для заряда 1.50V
			setSaveVoltage = 130;      // напряжение хранения 1.3V
			setActChargeCurrent = 10;   // ток активационного заряда 0.1A
			setChargeCurrent = 30;   	// ток заряда	0.3A
			setCutChargeCurrent = 2;   	// ток отсечки заряда CV 0,02A
			setDischargeCurrent = 50;	// ток разряда	0.5A			
		}
		else if ( BatVoltage > 200 && BatVoltage < 430 && ( batteryType != BAT_LIION)) {
			batteryType = BAT_LIION;
			setActVoltage = 290;       // напряжение окончания активации 2.90V
			setMinVoltage = 320;       // минимальное напряжение для разряда 3.20V
			setMaxVoltage = 420;       // максимальное напряжение для заряда 4.20V
			setSaveVoltage = 380;      // напряжение хранения 3.8V
			setActChargeCurrent = 10;   // ток активационного заряда 0.1A
			setChargeCurrent = 30;   	// ток заряда	0.3A
			setCutChargeCurrent = 5;   	// ток отсечки заряда CV 0,05A
			setDischargeCurrent = 50;	// ток разряда	0.5A			
		}
		else if ( BatVoltage > 450 && BatVoltage < 800 && ( batteryType != BAT_LEAD6V)){
			batteryType = BAT_LEAD6V;
			setActVoltage = 600;       // напряжение окончания активации 6V
			setMinVoltage = 610;       // минимальное напряжение для разряда 6.10V
			setMaxVoltage = 710;       // максимальное напряжение для заряда 7.10V
			setSaveVoltage = 630;      // напряжение хранения 6.3V
			setActChargeCurrent = 50;   // ток активационного заряда 0.5A
			setChargeCurrent = 150;   	// ток заряда	1.5A
			setCutChargeCurrent = 40;   // ток отсечки заряда CV 0,4A
			setDischargeCurrent = 150;	// ток разряда	1.5A
			}
		else if ( BatVoltage > 800 && BatVoltage < 1500 && ( batteryType != BAT_LEAD12V)){
			batteryType = BAT_LEAD12V;
			setActVoltage = 1200;       // напряжение окончания активации 12.00V
			setMinVoltage = 1220;       // минимальное напряжение для разряда 12.20V
			setMaxVoltage = 1420;       // максимальное напряжение для заряда 14.20V
			setSaveVoltage = 1270;      // напряжение хранения 12.7V
			setActChargeCurrent = 50;   // ток активационного заряда 0.5A
			setChargeCurrent = 150;   	// ток заряда	1.5A
			setCutChargeCurrent = 40;   // ток отсечки заряда CV 0,4A
			setDischargeCurrent = 150;	// ток разряда	1.5A
			}
}
/*****************************************************
 * Сохранить измененные параметры настроек
 * void aplySettings( bool edit)
*****************************************************/
void aplySettings( bool edit)
	{
		if ( edit) {
			workActChargeCurrent = setActChargeCurrent;	// ток активации 
			workChargeCurrent = setChargeCurrent;		// ток заряда
			workCutChargeCurrent = setCutChargeCurrent;	// ток отсечки заряда CV
			workDischargeCurrent = setDischargeCurrent;	// ток разряда
			workVoltagePause = setSaveVoltage;			// напряжение хранения	
		}
	}	
/*****************************************************
 * Подсчет полученной, отданной энергии в ампер часах
 * energyCalculation()
*****************************************************/
void energyCalculation(){
  if ( onTime > prevTime) { 
    prevTime = onTime;
    //chargeEnergy += ( (uint32_t)ChargeCurrent << 16) / 3600;
    //dischargeEnergy += ( (uint32_t)DischargeCurrent << 16) / 3600;
    chargeEnergy += ((float)ChargeCurrent) / 360000.0;
    dischargeEnergy += ((float)DischargeCurrent) / 360000.0;	
    }
}
/*****************************************************
 * сделать паузу по времени или падению напряжения
 * bool makeРause(uint32_t tp, int16_t voltage)
 * 		tp - время паузы в секундах, если 
 *		voltage - не равно 0, по падению напряжения
 *		ниже voltage * 
 * 		return false - пауза не окончена  
 * 		return true  - пауза закончилась  
 ****************************************************/
 bool makePause( uint32_t tpause, int16_t voltage)
 {
    static uint32_t tpStart = 0;
	if ( voltage)
		if ( BatVoltage >= voltage) return false;
		else return true;
    if ( !tpause) { 
      tpStart = 0;
      return true;
    }
    if ( !tpStart) tpStart = TimeWork;
    if (( TimeWork - tpStart) >= tpause) 
    {
      tpStart = 0;
      return true;
    }
    else return false;  
 }
/*************************************************************************************
 * Заряд аккумулятора СС - постоянным током до достижения voltage на аккумуляторе.
 * Если mincurrent не равен 0, то после достижения voltage, 
 * заряд CV - падающим током до mincurrent
 * 
 * bool ChargingBattery( uint16_t constcurrent, uint16_t mincurrent, uint16_t maxvoltage) 
 *				constcurrent - ток заряда Амперы
 *        mincurrent - min ток поддержания заряда
 *        voltage - предел заряда по напряжению
 * return false - заряд продолжается
 *        true  - по окончанию заряда
 ****************************************************/
bool ChargingBattery( uint16_t constcurrent, uint16_t mincurrent, uint16_t maxvoltage)
{
	uint16_t dPwm;	 			// дельта приращения pwm
	static bool fCV = false;	// флаг CV - поддержания напряжения
 
	/* если constcurrent=0, или достигнуто напряжение, при 0 mincurrent - останов заряда */
	if ( constcurrent == 0 || (( BatVoltage > maxvoltage) && ( mincurrent == 0))) {
endcharge:    		
		pwmCharge =	0;
		fCV = false;
		digitalWrite10( pwmCharge);		// ШИМ  = 0
		return true; 
		}
   
	if ( fCV || ( batteryType != BAT_LEAD12V)) dPwm = 1;					// если CV поддержание или LiIon
	  else if ( pwmCharge < 200) dPwm = 5;
		  else if ( pwmCharge < 500) dPwm = 2;
			  else if ( pwmCharge < 700) dPwm = 1;
				  else if ( pwmCharge < 900) dPwm = 1;
					  else if ( pwmCharge < 1000) dPwm = 1;
						  else dPwm = 1;
  
  if ( BatVoltage >= maxvoltage) 
  {
    fCV = true;
    if ( ChargeCurrent > mincurrent)
    {
      if ( pwmCharge > dPwm) {
        pwmCharge -= dPwm;
        goto retcharge;
      }
    } else goto endcharge;
  }
  if (( BatVoltage < (maxvoltage)) && fCV)
    pwmCharge += dPwm;
      
  if ( !fCV) {                	 
	  if ( ChargeCurrent < constcurrent) {
		  if ( pwmCharge < 1023) pwmCharge += dPwm;
	  } 
	  else 
	  {
		  if ( ChargeCurrent > (constcurrent))
		  	if ( pwmCharge > dPwm) pwmCharge -= dPwm;
	  }
  }
retcharge: 
	pwmWrite10( pwmCharge);
  return false;
}
/*****************************************************
 * Разряд аккумулятора постоянным током current
 * bool DischargingBattery( uint16_t current, uint16_t voltage) 
 *				current - ток разряда Амперы
 *        voltage - предел разряда по напряжению
 * return false  - если напряжение не занижено
 *        true - по окончанию разряда
*****************************************************/
bool DischargingBattery( uint16_t current, uint16_t voltage)
{
	uint16_t dPwm;	 // дельта приращения pwm
	
	if ( current == 0 || BatVoltage < voltage) {		// если ток=0, или занижено напряжение - останов заряда
		pwmDischarge =	0;
		digitalWrite9( pwmDischarge);		// ШИМ  = 0
		return true; 
		}
		
	if ( batteryType < BAT_LEAD6V) dPwm = 1;	
	if ( pwmDischarge < 200) dPwm = 10;
		else if ( pwmDischarge < 500) dPwm = 5;
			else if ( pwmDischarge < 700) dPwm = 2;
				else if ( pwmDischarge < 900) dPwm = 2;
					else if ( pwmDischarge < 1000) dPwm = 1;
						else dPwm = 1;	 
	if ( DischargeCurrent < current) {
		if ( pwmDischarge < 1023) pwmDischarge += dPwm;
	} else {
		if ( DischargeCurrent > (current + 2))
			if ( pwmDischarge >dPwm) pwmDischarge -= dPwm;
	}
	pwmWrite9( pwmDischarge);
  return	 false;
}
/*****************************************************
 * getVoltageCurrent() - считать напряжение и токи
*****************************************************/
void getVoltageCurrent(){
	uint16_t buf;
	{ // напряжение на батарее 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_Voltage);
		}
		buf = buf / 16;
		BatVoltage = uint16_t(buf) * 25 /10;
	}	
	{ // ток заряда 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_ChargeCurrent);
		}
		buf = buf / 16;
		ChargeCurrent = buf * 3;  // шунт - 3 резистора 0.1
	}
	{ // ток разряда 
		buf = 0;
		for (int i=0; i <= 16; i++) {
			buf += analogRead( pinADC_DischargeCurrent);
		}
		buf = buf / 16;
		DischargeCurrent = buf * 2;  // шунт - 2 резистора 0.1 в параллель
	}
	if ( BatVoltage > 50 && BatVoltage < 1500) 
		BatVoltage += (1500-BatVoltage)/200; 	// поправка для низких напряжений
	BatVoltage += ((-ChargeCurrent)/30 + DischargeCurrent/20); // 3 и 2 число резисторов по 0.1 ома
}
/*****************************************************
 * getTimeWork() - время работы сеанса
*****************************************************/
void getTimeWork()
{
	static bool fPrevStart = false;
	static uint32_t timeStart = 0;						// время старта работы
	onTime = millis() / 1000ul;							// время от включения прибора
	if ( (workPhase != STOP) && !fPrevStart) {	 
		fPrevStart = true;
		timeStart = millis();
	}
	if ( (workPhase != STOP)) {						// если не останов
		TimeWork = (millis() - timeStart) / 1000ul;		// полное количество секунд работы	
	} else {
		TimeWork = 0;
		timeStart = 0;
		fPrevStart = false; 
	}	
	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) >= 250) 		// обновление экрана 4 раза в секунду
		tDisplay = ttt;
	else return;
	
	display.setTextSize( 2);
	display.clearDisplay();
	display.setCursor(0,0);
	if ( frame == FRAME_INFO) {
		int16_t current = ( ChargeCurrent > DischargeCurrent ? ChargeCurrent : -DischargeCurrent);
		sprintf(bufStr, "Vb %02d.%02dv", BatVoltage/100, BatVoltage%100);
		display.println(bufStr);
		sprintf(bufStr, "Ic %02d.%02da", current/100, abs(current)%100);
		display.println(bufStr);
		uint32_t dt = ( onTime + 1)%4;
		if ( dt == 1 || dt == 2) { 
			sprintf(bufStr, "T%3d:%02d:%02d", timeHours, timeMins, timeSecs);	
			display.print(bufStr);
			sprintf(bufStr, "Pwm %04d", pwmCharge > pwmDischarge ? pwmCharge : -pwmDischarge);
			display.println(bufStr);
		} 
		else if ( dt == 3) {		
			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);
		} else {
				display.print( F("----------"));
				if ( workPhase == STOP) display.print( F(" STOP")); 
				else if ( workPhase == ACTIVATION) display.print( F("ACTIVATION")); 
				else if ( workPhase == CHARGE) display.print( F(" CHARGE")); 
				else if ( workPhase == CH_PAUSE || workPhase == DSC_PAUSE ) display.print( F(" PAUSE"));
				else if ( workPhase == DISCHARGE) display.print( F(" DISCHARGE"));
			}
	} 
	else if ( frame == FRAME_START) {
		sprintf(bufStr, "Vb %02d.%02dv", BatVoltage/100, BatVoltage%100);
		display.println(bufStr);		
		display.setTextSize( 3);
		display.setCursor(0,24);
		if ( workPhase != STOP) display.println( F(" STOP?"));
		else display.println( F(" START?"));
	} 
	else if ( frame == FRAME_MODE) {
		if ( markedLineMode < 5) {
			display.print( F(" CC charge"));
			display.print( F(" CCCV chrg"));
			display.print( F(" CC ch/dis"));
			display.print( F(" CCCV ch/d"));
			display.setCursor( 0, ( markedLineMode -1 ) *16);			
		} else if ( markedLineMode < 9) {
			display.print( F(" Discharge"));
			display.println( F(" ..."));
			display.println( F(" ..."));
			display.print( F(" ..."));
			display.setCursor( 0, ( markedLineMode -5 ) *16);
		}
		display.print( F("> "));		
	} 
	else if ( frame == FRAME_VOLTAGE) {
		display.print( F(" Uact")); display.print( (float)setActVoltage / 100.0);
		if ( setActVoltage < 1000) display.print( F( "\n"));
		display.print( F(" Umin")); display.print( (float)setMinVoltage / 100.0);
		if ( setMinVoltage < 1000) display.print( F( "\n"));		
		display.print( F(" Umax")); display.print( (float)setMaxVoltage / 100.0);
		if ( setMaxVoltage < 1000) display.print( F( "\n"));
		display.print( F(" Usav")); display.print( (float)setSaveVoltage / 100.0);
		if ( setSaveVoltage < 1000) display.print( F( "\n"));
		display.setCursor( 0, ( marked -1 ) *16);
		if ( fEdit) display.print( F("* "));
		else display.print( F("> "));
	} 
	else if ( frame == FRAME_CURRENT) {
		display.print( F(" Ichg ")); display.print( (float)setChargeCurrent / 100.0);
		display.print( F(" Icut ")); display.print( (float)setCutChargeCurrent / 100.0);
		display.print( F(" Idis ")); display.print( (float)setDischargeCurrent / 100.0);
		display.print( F(" cycles ")); display.print( nCycles);
		display.setCursor( 0, ( marked -1 ) *16);
		if ( fEdit) display.print( F("* "));
		else display.print( F("> "));
	} 
	display.display();
}
/****************************************************
 * uint8_t getButtonState() опрос кнопок
*****************************************************/
uint8_t getButtonState()
{	
	static uint32_t pressTime = 0;         // время нажатия кнопки
	static uint8_t button;
	uint8_t state = (~PIND & 0b1111100) >> 2;
	if ( state && !pressTime) 
	{ 
		pressTime = millis();
		button = state;
	}
	if ( !state && pressTime) 
	{
		if ( ( millis() - pressTime) > 150)
		{
			pressTime = 0;
			return button;
		}
	}
	return 0;
}
/*****************************************************
 * Обработка по нажатию кнопок
 * void procPresButtons()
*****************************************************/
void procPresButtons()
{
	uint8_t button = getButtonState();
	
	switch ( button)
	{
		case BUT_OK:	// переход на выбор режима работы с последующим стартом или останов
			if ( curFrame == FRAME_INFO) {
				if ( workPhase != STOP) curFrame = FRAME_START;
				else curFrame = FRAME_MODE;				
			}
			else if ( curFrame == FRAME_START) {
				if ( workPhase != STOP) 
				{	
					workPhase = STOP;		
					digitalWrite(pinLedWork, 0);
				} else {
					cycleCounter = 0;		// по старту сброс накопленных показаний
					chargeEnergy = 0;
					dischargeEnergy = 0;
					workPhase = ACTIVATION;
					digitalWrite(pinLedWork, 1); 
				}
				curFrame = FRAME_INFO;
			}
			else if ( curFrame == FRAME_MODE) {	// выбор режима перед стартом
				
				if ( markedLineMode == 1) {	// CC ch save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = 0;
					workDischargeCurrent = 0;
					workVoltagePause = setSaveVoltage;
				}
				if ( markedLineMode == 2) { // CV ch save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = setCutChargeCurrent;
					workDischargeCurrent = 0;
					workVoltagePause = setSaveVoltage;					
				}
				if ( markedLineMode == 3) { // CC ch/dis no save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = 0;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 4) { // CV ch/dis no save
					workActChargeCurrent = setActChargeCurrent;
					workChargeCurrent = setChargeCurrent;
					workCutChargeCurrent = setCutChargeCurrent;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				if ( markedLineMode == 5) { // discharge 
					workActChargeCurrent = 0;
					workChargeCurrent = 0;
					workCutChargeCurrent = 0;
					workDischargeCurrent = setDischargeCurrent;
					workVoltagePause = 0;
				}
				curFrame = FRAME_START;
			}
			else if ( curFrame == FRAME_VOLTAGE || curFrame == FRAME_CURRENT)	
				fEdit = !fEdit;
			break;

		case BUT_RIGHT:
			if ( workPhase == STOP && !fEdit) {
				markedLine = 1;
				if ( curFrame == FRAME_CURRENT) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_MODE) curFrame = FRAME_INFO;
					else curFrame++;
			} 
			else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
			break;
		case BUT_LEFT:
			if ( workPhase == STOP && !fEdit) {
				markedLine = 1;
				if ( curFrame == FRAME_INFO) curFrame = FRAME_CURRENT;
				else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
				else if ( curFrame == FRAME_MODE) curFrame = FRAME_INFO;
					else curFrame--; 
			} 
			else if ( curFrame == FRAME_START) curFrame = FRAME_INFO;
			break;

		case BUT_UP:
		case BUT_DOWN:
			if ( fEdit) {
				if ( curFrame == FRAME_VOLTAGE){
					if ( markedLine == 1){
						button == BUT_UP ? setActVoltage += 10 : setActVoltage -= 10;
						if ( setActVoltage < 100) setActVoltage = 100;
						else if ( setActVoltage > 1200) setActVoltage = 1200;
					}
					if ( markedLine == 2){
						button == BUT_UP ? setMinVoltage += 10 : setMinVoltage -= 10;
						if ( setMinVoltage < 110) setMinVoltage = 110;
						else if ( setMinVoltage > 1220) setMinVoltage = 1220;
					}
					if ( markedLine == 3){
						button == BUT_UP ? setMaxVoltage += 10 : setMaxVoltage -= 10;
						if ( setMaxVoltage < 150) setMaxVoltage = 150;
						else if ( setMaxVoltage > 1620) setMaxVoltage = 1620;
					}
					if ( markedLine == 4){
						button == BUT_UP ? setSaveVoltage += 10 : setSaveVoltage -= 10;
						if ( setSaveVoltage < 130) setSaveVoltage = 130;
						else if ( setSaveVoltage > 1280) setSaveVoltage = 1280;
					}					
				}
				if ( curFrame == FRAME_CURRENT){
					if ( markedLine == 1){
						button == BUT_UP ? setChargeCurrent += 10 : setChargeCurrent -= 10;
						if ( setChargeCurrent < 10) setChargeCurrent = 10;
						else if ( setChargeCurrent > 500) setChargeCurrent = 500;
					}
					if ( markedLine == 2){
						button == BUT_UP ? setCutChargeCurrent += 10 : setCutChargeCurrent -= 10;
						if ( setCutChargeCurrent < 10) setCutChargeCurrent = 10;
						else if ( setCutChargeCurrent > 100) setCutChargeCurrent = 100;
					}
					if ( markedLine == 3){
						button == BUT_UP ? setDischargeCurrent += 10 : setDischargeCurrent -= 10;
						if ( setDischargeCurrent < 10) setDischargeCurrent = 10;
						else if ( setDischargeCurrent > 500) setDischargeCurrent = 500;
					}
					if ( markedLine == 4){
						button == BUT_UP ? nCycles++ : nCycles--;
						if ( nCycles < 1) nCycles = 1;
						else if ( nCycles > 99) nCycles = 99;
					}					
				}					
			}			
			if ( curFrame > FRAME_INFO && curFrame < FRAME_MODE && !fEdit) {
				button == BUT_UP ? markedLine-- : markedLine++;
				if ( !markedLine) markedLine++;
				else if ( markedLine > 4) markedLine--;
			} 
			else if ( curFrame == FRAME_MODE) {
				button == BUT_UP ? markedLineMode-- : markedLineMode++;
				if ( !markedLineMode) markedLineMode++;
				else if ( markedLineMode > 5) markedLineMode--;
			}
			break;
	}
}

 

neid86@gmail.com
Offline
Зарегистрирован: 28.10.2014

Зачем так много, достаточно 1кГц для заряда))
Чего вы пытаетесь этим добиться на такой частоте, и какое заполнение ШИМ тогда?

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

neid86@gmail.com пишет:
Зачем так много, достаточно 1кГц для заряда)) Чего вы пытаетесь этим добиться на такой частоте, и какое заполнение ШИМ тогда?

Это древняя шумерская технология, в частности  примененная в своё время парфянами  и ныне почти забытая;)
Чтобы расшифровать смысл ветхих манускриптов парфян пришлось много поработать. Для вас эта технология открыта сейчас, достаточно изучить скетч и постигнуть принцип работы приложенной схемы..
English edition:
This is an ancient Sumerian technology, in particular, used at one time by the Parthians and now almost forgotten;)      It took a lot of work to decipher the meaning of the old Parthian manuscripts. For you, this technology is open now, it is enough to study the program code and comprehend the principle of operation of the attached scheme..

neid86@gmail.com
Offline
Зарегистрирован: 28.10.2014

Ну как же шумерская, если половина компонентов нет, не заработает по шумерски)) будет липой парфянской!
Основной принцип на словах озвучивается, а код чужой читать без коментов в самых г местах, неее... Думал человек скажет, а он парфянин из шумерии)))

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

neid86, в этом топике, у меня нет желания и времени устраивать курсы по введению в широтноимпульсную технологию источников питания и их реинкарнаций. Но поскольку я древний мамонты и такой же добрый, рекомендую вам завести топик по этой теме на ветке "Аппаратные вопросы", где я и более продвинутые в этом вопросе форумчане, с радостью поделятся своими знаниями ;)

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

Раскрывая крохи секретов древних шумерских технологий ;)
Осциллограммы заряда и разряда свинцового аккумулятора, голубой луч ШИМ, желтый - напряжение на соответствующих шунтах:
 
ps. Пришла, наконец то, "моя дорогая, прелесть" на 2.4 дюйма

Вторая 24V9A, правда, задерживается :(

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

Фух. Завершена самая противная часть марлезонского балета - упаковка в корпус.
Материал корпуса - листовое оргстекло 5 мм, гнутое с нагревом.. и непечатными словами.


Теперь можно в свободное время, потягивая пользительные напитки, не торопясь, допиливать прошивку и рисовать надписи на лицевой панели.
Дисплей SSD1309 в отличии от SSD1306, кушает электроэнергию как не в себя, около 200 mA, грея при этом 7805.
На ней рассеивается порядка 3 Вт, посему ТО-220 и теплоотвод рекомендуются.

зы: Вместо R1 в этом случае ставится перемычка.

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Domosed пишет:

Теперь можно в свободное время, потягивая пользительные напитки, не торопясь, допиливать прошивку и рисовать надписи на лицевой панели.

не уж то производство Байкала и Тархуна по оригинальной рецептуре, а не как в 90-е (вода из лужи порошок из Германии) возобновили? )))

-NMi-
Offline
Зарегистрирован: 20.08.2018

Пользительные напитки это что, например???

SLKH
Offline
Зарегистрирован: 17.08.2015

Domosed пишет:

Охлаждение, просто и надежно, рекомендую.

Биполярный транзистор в этой схеме закономерно греется.
А mosfet, какой-никакой, но ключевой элемент.

Вот с каких пользительных напитков в этой схеме он ключевой? И какое отношение к этой схеме имеет Rds? И что, mosfet в этой схеме не греется?

yucan
Offline
Зарегистрирован: 20.04.2015

Пиво, естественно)

-NMi-
Offline
Зарегистрирован: 20.08.2018

Чотт нет там на схеме пива, аккурат правее вентилятора должна стоять кружка с пользительным напитком.

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

-NMi- пишет:
Пользительные напитки это что, например???

Пиво рыхлит мозг и тело, и посему..
Вишневка, например. Употребляя, вникаешь в броуновское движение витаминов :)

SLKH пишет:
Вот с каких пользительных напитков в этой схеме он ключевой? И какое отношение к этой схеме имеет Rds? И что, mosfet в этой схеме не греется?

Тут я ошибся конечно, работает в линейном режиме, но больно хорошо SK3918 лёг на плату ;)

dorian
Offline
Зарегистрирован: 25.02.2018

Автор темы, отзовитесь.

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

dorian пишет:
Автор темы, отзовитесь.

- Алло, Domosed на линии, слушаю вас. Что случилось?
Извиняюсь, что пропал, сейчас много "всяческих проектов" готовится к реализации. Пытаюсь программировать на Kotlin и т.д.

И , да, кстати доделал ручку-паяльник на  LGT8F328P, если интересно - напишу. 

Oleg Smirnoff
Offline
Зарегистрирован: 06.01.2018

А как дела с ЗУ ?
интересно же, есть у меня примитивное ЗУ с трансформатором - 12/24 вольт 20 ампер,
Хочется из него сделать нечто удобоваримое.
С интересом прочитал ваш опыт.

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

Oleg Smirnoff пишет:

А как дела с ЗУ ?..

Дела с ЗУ идут хорошо, к сожалению не так быстро как хотелось бы.
Экспериментирую с различными режимами работы и их параметрами.
Пока у меня нет ясности по оптимальному алгоритму восстановления автомобильных АКБ, но есть обнадеживающие результаты. И ЗУ уже может работать полностью в автоматическом режиме.
Как показала практика, разработанная и выше представленная схема имеет отличный потенциал для поиска и отработки алгоритмов анализа состояния, заряда, восстановления автомобильных АКБ.
Но необходимо добавить блок отключения питания OLED, Arduino, и вентилятора при пропадании внешнего питания. Хотя бы в виде реле. И добавить контроль за работой вентилятора, что по сути несложно.
Работа ведётся.. На днях поделюсь материалами.

Для затравки проба фазы перемешивания электролита для дестратификации.

Oleg Smirnoff
Offline
Зарегистрирован: 06.01.2018

День добрый !
 

"Пока у меня нет ясности по оптимальному алгоритму восстановления автомобильных АКБ"

Не хочу подрывать ваш энтузиазм - но вот на моей памяти - еще с тех лет когда я школьником читал свой любимый журнал "Радио" - множество людей пытались добиться "ясности" в этом неблагодарном деле "восстановления автомобильных АКБ".
50 лет прошло - а воз и ныне там. :(
сколько людей, столько мнений, столько алгоритмов, столько же и "успехов" - которые почему то (?) не имеют сколь ни будь далеко идущих последствий и повторений успешных процессов восстановления свинцовых АКБ.
Ни в коем случае не навязываю своего мнения - но может просто остановится на оптимальных алгоритмах заряда-разряда, объективного контроля состояния АКБ - чтобы продлить срок службы новой батареи - уже и это достойный результат ?
тем не менее -позвольте высказать свое уважение за серьезную работу по теме, как только разгребусь с текучкой - непременно займусь повторением вашего проекта.
Уж больно "донор хорош" - добротное , хоть и крайне примитивное  ЗУ Alpine-50, 20А 12/24V с мощным трансформатором и выпрямителем - и достаточным простором внутри металлического  корпуса, руки чешутся, а в Китае заказаны  кое какие компоненты для апгрейда. :)
Всех благ - и успеха !