// 2021
//Электронная нагрузка by BSP.
#include "wiring_private.h"
//================================подключение к Ардуино
#define CurrentSensePin A1
#define VoltageSensePin A2
#define MosfetDrivePin 9 //можно сменить на 10, только на эти пины настроен быстрый ШИМ
#define voltDividerPin A3
//-------------------------------------------НАСТРОЙКА---------------------------------------
#define highDivRatio 20 // 20.7; //Делитель напряжения в измерителе 1:20
#define lowDivRatio 5.025 //Делитель напряжения в измерителе 1:5
float sensorResOhm = 0.1 ;// 0.1 ом токовый шунт
word vref = 1088;//1074;//1100; Измерить данное значение у себя на Ардуино и подставить сюда ( в милливольтах).
float calibrationCurr =1;// измеренный ток будет домножаться на это значение, без необходимости лучше не трогать
int offsetCurr = 0; //25 если ток неверный всегда на одно и то же значение, подставим его сюда.
//положительное число занижает реальный ток, отрицательное - завышает
bool csvRepStyle = true; //по умолчанию отчёт в режиме CSV
String delim = ";"; //разделитель для CSV
int reportInterval = 2000;//интервал выдачи показаний на COM порт, мс
float calibrationVolt =1 ;//измеренное напряжение будет домножаться на это значение, без необходимости лучше не трогать
word dischMode =1; //1 для СС режима, 2 для constant power, 3 для constant resistance
//===============================Первоначальная настройка напряжения и тока
word targetCurrent = 150;// mA первоначальный ток разряда, который будет отображаться в меню
word CutOffVoltage = 900 ;//mV Первоначальное напряжение отсечки
word MinVoltThreshold = 500; //mV минимальное первоначальное напряжение, ниже которого разряд не запустится
word targetPower = 4000;
word constRes = 10000;
/////////////////////////////////////////////ПИД-Регулятор///////////////////////////////////////////////////////_____________________________________
float Kp = .1; //без причины лучше не трогать
int Kd = 1; //без причины лучше не трогать
////////////////////////////////////////////////////-----флаги МЕНЮ
byte set = 0;
bool set2Entered =0;
bool set3Entered =0;
bool set6Entered =0;
bool set7Entered =0;
bool showMainMenu = 1;
////////////////////////// Время
unsigned long currentTime = 0;
unsigned long lastTime = 0; //для интервала репортинга
unsigned long lastMillis = 0;// храним время для интервала подсчёта емкости.
unsigned long dischStart; // Время начала разряда
///////////////////////////////////////////////
char valChar[6];
String valString;
float currentValueNow = 0;
float voltageValueNow = 0;
float powerValueNow =0;
float mah =0;
float mwh =0;
bool div5VState = false; //отслеживающая состояние делителя 1/5 --true, 1/20-- false
int error; // ошибка для ПД регулятора
int prevErr = 0;
int dError;
int power =0; //Это значение подаётся на транзистор через analogWrite
int prevPower =0 ;
int powerCorrection = 0; //значение - результат вычисления PID, корректирующее ток до таргета.
///////////////////////////////////////////////
float voltageDividerRatio = highDivRatio;
void setup()
{
analogReference (INTERNAL);
pinMode(CurrentSensePin, INPUT);
pinMode(VoltageSensePin, INPUT);
pinMode(MosfetDrivePin, OUTPUT);
max5Volt (false);
analogWrite (MosfetDrivePin,0);
Serial.begin(9600);
//частота преобразования АЦП - делитель 32
cbi(ADCSRA, ADPS1);
// Пины D9 и D10 - 2 кГц 10bit ШИМ
TCCR1A = 0b00000011; // 10bit
TCCR1B = 0b00001010; // x8 fast pwm
}
void loop()
{
if (showMainMenu==1) {
currentValueNow = calcCurrentNew (5) ;
voltageValueNow = calcVoltageNew (10);
Serial.println ("---------MAIN MENU-------");
Serial.print ("VoltageValueNow");
Serial.print (" ");
Serial.println (voltageValueNow,0);
Serial.print ("CurrentValueNow");
Serial.print (" ");
Serial.println (currentValueNow,0);
switch (dischMode) {
case 1:
Serial.print ("Target current (mA):");
Serial.print (" ");
Serial.println (targetCurrent);
break;
case 2:
Serial.print ("Target Power (mW):");
Serial.print (" ");
Serial.println (targetPower);
break;
case 3:
Serial.print ("Constant resistance (mOhm):");
Serial.print (" ");
Serial.println (constRes);
break;
}
Serial.print ("Cut Off Voltage(mV):");
Serial.print (" ");
Serial.println (CutOffVoltage);
Serial.println (" ");
Serial.println("Press 7 to change discharge mode");
Serial.println("Press 6 to change to CSV report style");
Serial.println("Press 5 to open mosfet (4debug)"); //режим отладки (приоткрываем стразистор)
Serial.println("Press 4 to see voltage sensor readings (4debug)");
Serial.println("Press 3 to set Cut off voltage in mV");
switch (dischMode) {
case 1:
Serial.println("Press 2 to set discharge current in mA");
break;
case 2:
Serial.println("Press 2 to set discharge power in mW");
break;
case 3:
Serial.println("Press 2 to set discharge resistance mOhm");
break;
}
Serial.println("Press 1 to start");
Serial.println("Press 0 to go to main menu"); //выход из режимов
showMainMenu =0;
}
if (Serial.available() > 0&& set==0 )
{
int val=Serial.read(); //прочитать что было послано в порт
switch(val)
{
case 48: analogWrite(MosfetDrivePin, 0); set=0; showMainMenu = 1; break; //если приняли 0 остановить разряд, выбрать 0 режим и открыть менюшку
case 49: set=1; break; //если приняли 1 то запустить режим 1
case 50: set=2; break; //если приняли 2 то запустить режим 2
case 51: set=3; break; //если приняли 3 то запустить режим 3
case 52: set =4; break; ////если приняли 4 то запустить режим 3
case 53: set =5; break;
case 54: set =6; break;
case 55: set =7; break;
}
}
/////////////////////////////////////////////////////////////////////////////////////////START STOP
if (set ==1)
{
currentValueNow = calcCurrentNew (5) ;
voltageValueNow = calcVoltageNew (20);
if (voltageValueNow <= MinVoltThreshold)
{
Serial.print ("No discharge -- Voltage is lower than threshold ");
Serial.print (MinVoltThreshold);
Serial.print (" ");
Serial.println ("mV");
set = 0;
showMainMenu =1;
}
else
{
Serial.println ("Starting discharge.. ");
Serial.println (F("time;Current, mA;Voltage,mV;mAh;mwh;mW"));
int calc = 0;
mah = 0;
mwh =0;
dischStart = millis ();
if (dischMode ==1 )
{
while ( set ==1 && voltageValueNow > CutOffVoltage)
{
currentValueNow = calcCurrentNew (5) ;//5
voltageValueNow = calcVoltageNew (5);//3
if (millis() - lastTime > reportInterval ) ///show current readings every interval without stopping PID
{
if (csvRepStyle ==true)
{
mah+= currentValueNow * (millis()-lastTime)/3600000;
mwh+= (currentValueNow * (millis()-lastTime)/3600000)*voltageValueNow/1000.00;
Serial.print((millis() - dischStart) / 1000);
Serial.print (delim);
Serial.print (currentValueNow,0);
Serial.print (delim);
Serial.print (voltageValueNow,0);
Serial.print (delim);
Serial.print (mah);
Serial.print (delim);
Serial.println (mwh);
calc = 0;
lastTime = millis ();
}
else
{
Serial.print ("Сurrent:");
Serial.print (currentValueNow,0);
Serial.println (" mA");
Serial.print ("Voltage:");
Serial.print (voltageValueNow,0);
Serial.println (" mV");
mah+= currentValueNow * (millis()-lastTime)/3600000;
mwh+= (currentValueNow * (millis()-lastTime)/3600000)*voltageValueNow/1000;
Serial.print ("mah:");
Serial.println (mah);
Serial.print ("mwh:");
Serial.println (mwh);
Serial.print ("Mosfet Power:");
Serial.println (power);
Serial.print ("Calcs/interval:");
Serial.println (calc);
calc = 0;
lastTime = millis ();
}
}
error = targetCurrent - currentValueNow; // current error
dError = error - prevErr;
powerCorrection = error * Kp + dError * Kd ;
if (powerCorrection > 1023) //добавлено как защита от переполнения
{
powerCorrection = 1023;//добавлено как защита от переполнения
}
if (powerCorrection < -1023)//добавлено как защита от переполнения
{
powerCorrection = -1023;//добавлено как защита от переполнения
}
power = prevPower + powerCorrection; // Корректируем текущее значение ШИМ, в зависимости от того, выше таргета ток, или ниже.
if (power > 1023)
{
power = 1023;
}
if (power < 0)
{
power = 0;
}
if (power ==255) // мы поправили таймер и он стал 10 битным, однако в ф-ции analogWrite прописано что еслии 255 то digitalWrite 1.
//с 10-битным таймером это неверно.
{
power = 254;
}
analogWrite(MosfetDrivePin, power);
prevPower = power; // Корректируем текущее значение ШИМ, в зависимости от того, выше таргета ток, или ниже.
prevErr = error;
if (Serial.available() > 0 ) //прерываемся и выходим по нажатию 0
{
int val2=Serial.read();
if (val2 ==48) {analogWrite(MosfetDrivePin, 0); set = 0; showMainMenu =1;}
Serial.println ("Discharge interrupted by user ");
}
calc++;
}
}
//end dischMode==1
else if (dischMode ==2 )
{
while ( set ==1 && voltageValueNow > CutOffVoltage)
{
currentValueNow = calcCurrentNew (5) ;//5
voltageValueNow = calcVoltageNew (5);//3
powerValueNow = currentValueNow * voltageValueNow/1000.00;
if (millis() - lastTime > reportInterval ) ///show current readings every interval without stopping PID
{
if (csvRepStyle ==true)
{
mah+= currentValueNow * (millis()-lastTime)/3600000;
mwh+= (currentValueNow * (millis()-lastTime)/3600000)*voltageValueNow/1000.00;
Serial.print((millis() - dischStart) / 1000);
Serial.print (delim);
Serial.print (currentValueNow,0);
Serial.print (delim);
Serial.print (voltageValueNow,0);
Serial.print (delim);
Serial.print (mah);
Serial.print (delim);
Serial.print (mwh);
Serial.print (delim);
Serial.println (powerValueNow);
calc = 0;
lastTime = millis ();
}
else
{
Serial.print ("Сurrent:");
Serial.print (currentValueNow,0);
Serial.println (" mA");
Serial.print ("Voltage:");
Serial.print (voltageValueNow,0);
Serial.println (" mV");
mah+= currentValueNow * (millis()-lastTime)/3600000;
mwh+= (currentValueNow * (millis()-lastTime)/3600000)*voltageValueNow/1000;
Serial.print ("mah:");
Serial.println (mah);
Serial.print ("mwh:");
Serial.println (mwh);
Serial.print ("Power,mW:");
Serial.println (powerValueNow);
Serial.print ("Mosfet Power:");
Serial.println (power);
Serial.print ("Calcs/interval:");
Serial.println (calc);
calc = 0;
lastTime = millis ();
}
}
targetCurrent = targetPower*1000.00/voltageValueNow;
error = targetCurrent - currentValueNow; // current error
dError = error - prevErr;
powerCorrection = error * Kp + dError * Kd ;
if (powerCorrection > 1023) //добавлено как защита от переполнения
{
powerCorrection = 1023;//добавлено как защита от переполнения
}
if (powerCorrection < -1023)//добавлено как защита от переполнения
{
powerCorrection = -1023;//добавлено как защита от переполнения
}
power = prevPower + powerCorrection; // Корректируем текущее значение ШИМ, в зависимости от того, выше таргета ток, или ниже.
if (power > 1023)
{
power = 1023;
}
if (power < 0)
{
power = 0;
}
if (power ==255) // мы поправили таймер и он стал 10 битным, однако в ф-ции analogWrite прописано что еслии 255 то digitalWrite 1.
//с 10-битным таймером это неверно.
{
power = 254;
}
analogWrite(MosfetDrivePin, power);
prevPower = power; // Корректируем текущее значение ШИМ, в зависимости от того, выше таргета ток, или ниже.
prevErr = error;
if (Serial.available() > 0 ) //прерываемся и выходим по нажатию 0
{
int val2=Serial.read();
if (val2 ==48) {analogWrite(MosfetDrivePin, 0); set = 0; showMainMenu =1;}
Serial.println ("Discharge interrupted by user ");
}
calc++;
}
}//end dischMode==2
else if (dischMode ==3 )
{
while ( set ==1 && voltageValueNow > CutOffVoltage)
{
currentValueNow = calcCurrentNew (5) ;//5
voltageValueNow = calcVoltageNew (5);//3
powerValueNow = currentValueNow * voltageValueNow/1000.00;
if (millis() - lastTime > reportInterval ) ///show current readings every interval without stopping PID
{
if (csvRepStyle ==true)
{
mah+= currentValueNow * (millis()-lastTime)/3600000;
mwh+= (currentValueNow * (millis()-lastTime)/3600000)*voltageValueNow/1000.00;
Serial.print((millis() - dischStart) / 1000);
Serial.print (delim);
Serial.print (currentValueNow,0);
Serial.print (delim);
Serial.print (voltageValueNow,0);
Serial.print (delim);
Serial.print (mah);
Serial.print (delim);
Serial.print (mwh);
Serial.print (delim);
Serial.println (powerValueNow);
calc = 0;
lastTime = millis ();
}
else
{
Serial.print ("Сurrent:");
Serial.print (currentValueNow,0);
Serial.println (" mA");
Serial.print ("Voltage:");
Serial.print (voltageValueNow,0);
Serial.println (" mV");
mah+= currentValueNow * (millis()-lastTime)/3600000;
mwh+= (currentValueNow * (millis()-lastTime)/3600000)*voltageValueNow/1000;
Serial.print ("mah:");
Serial.println (mah);
Serial.print ("mwh:");
Serial.println (mwh);
Serial.print ("Mosfet Power:");
Serial.println (power);
Serial.print ("Calcs/interval:");
Serial.println (calc);
calc = 0;
lastTime = millis ();
}
}
targetCurrent = voltageValueNow*1000.00/constRes;
error = targetCurrent - currentValueNow; // current error
dError = error - prevErr;
powerCorrection = error * Kp + dError * Kd ;
if (powerCorrection > 1023) //добавлено как защита от переполнения
{
powerCorrection = 1023;//добавлено как защита от переполнения
}
if (powerCorrection < -1023)//добавлено как защита от переполнения
{
powerCorrection = -1023;//добавлено как защита от переполнения
}
power = prevPower + powerCorrection; // Корректируем текущее значение ШИМ, в зависимости от того, выше таргета ток, или ниже.
if (power > 1023)
{
power = 1023;
}
if (power < 0)
{
power = 0;
}
if (power ==255) // мы поправили таймер и он стал 10 битным, однако в ф-ции analogWrite прописано что еслии 255 то digitalWrite 1.
//с 10-битным таймером это неверно.
{
power = 254;
}
analogWrite(MosfetDrivePin, power);
prevPower = power; // Корректируем текущее значение ШИМ, в зависимости от того, выше таргета ток, или ниже.
prevErr = error;
if (Serial.available() > 0 ) //прерываемся и выходим по нажатию 0
{
int val2=Serial.read();
if (val2 ==48) {analogWrite(MosfetDrivePin, 0); set = 0; showMainMenu =1;}
Serial.println ("Discharge interrupted by user ");
}
calc++;
}
}
analogWrite(MosfetDrivePin, 0);
delay (100);
Serial.println ("Discharge completed ");
set = 0;
}
}
//////////////////////////////////////////////////////ПУНКТ2//////////////////////////////////////////////
if (set ==2) //set discharge current in mA
{
if (set2Entered ==0)
{
switch (dischMode) {
case 1:
Serial.print ("Enter discharge current, mA ");
Serial.println ("Press 0 to exit to main menu");
break;
case 2:
Serial.print ("Enter discharge power, mW ");
Serial.println ("Press 0 to exit to main menu");
break;
case 3:
Serial.print ("Enter discharge resistance,mOhm ");
Serial.println ("Press 0 to exit to main menu");
break;
}
set2Entered =1;
}
if (Serial.available() > 0 )
{
valString = Serial.readStringUntil('\n'); //прочитать всё
int val;
Serial.println(valString);
valString.toCharArray(valChar,sizeof(valChar));
val = atof(valChar);
valString = "";
if (val ==0)
{
set2Entered =0;
set = 0;
showMainMenu =1;
}
else {
switch (dischMode) {
case 1:
targetCurrent = val;
Serial.print ("Target current is set to ");
Serial.print (targetCurrent);
Serial.println ("mA");
break;
case 2:
targetPower = val;
Serial.print ("Target power is set to ");
Serial.print (targetPower);
Serial.println ("mW");
break;
case 3:
constRes = val;
Serial.print ("Constant resistance is set to ");
Serial.print (constRes);
Serial.println ("mOmh");
break; }
}
set = 0;
set2Entered =0;
showMainMenu =1;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
if (set ==3) //set Cut off voltage in mV
{
if (set3Entered ==0)
{
Serial.println ("Enter cut-off voltage. Press 0 to exit to main menu");
set3Entered =1;
}
if (Serial.available() > 0 )
{
valString = Serial.readStringUntil('\n'); //прочитать всё
int val;
Serial.println(valString);
valString.toCharArray(valChar,sizeof(valChar));
val = atof(valChar);
valString = "";
if (val ==0)
{
set = 0;
set3Entered =0;
showMainMenu =1;
}
else {CutOffVoltage = val;}
Serial.print ("Cut-off voltage is set to ");
Serial.print (CutOffVoltage);
Serial.println ("mV");
set = 0;
set3Entered =0;
showMainMenu =1;
}
}
if (set==4)
{
while (set==4)
{
currentValueNow = calcCurrentNew(3) ;
voltageValueNow = calcVoltageNew(3);
Serial.print ("Voltage:");
Serial.print (voltageValueNow);
Serial.println (" mV");
Serial.print ("Raw value from DAC ");
Serial.println (calcVoltageNewDAC (3));
delay (500);
if (Serial.available() > 0 ) //прерываемся и выходим по нажатию 0
{
int val2=Serial.read();
if (val2 ==48) {analogWrite(MosfetDrivePin, 0);set = 0; showMainMenu =1;}
}
}
}
if (set ==5)
{
while (set==5)
{
analogWrite(MosfetDrivePin, 750);//90ma at 750 @4V
currentValueNow = calcCurrentNew(5) ;
voltageValueNow = calcVoltageNew(5);
Serial.print ("Сurrent:");
Serial.print (currentValueNow);
Serial.println (" mA");
Serial.print ("Voltage:");
Serial.print (voltageValueNow);
Serial.println (" mV");
delay (500);
if (Serial.available() > 0 ) //прерываемся и выходим по нажатию 0
{
int val2=Serial.read();
if (val2 ==48) {analogWrite(MosfetDrivePin, 0); delay (100);set = 0; showMainMenu =1;}
}
}
}
if (set ==6)
{
if (set6Entered ==0)
{
Serial.println ("Set Report Style. Press 2 for csv style, 1 for normal style.");
Serial.println ("Press 0 to exit to main menu");
set6Entered =1;
}
if (Serial.available() > 0 )
{
valString = Serial.readStringUntil('\n'); //прочитать всё
int val;
Serial.println(valString);
valString.toCharArray(valChar,sizeof(valChar));
val = atof(valChar);
valString = "";
if (val ==0)
{
set = 0;
set6Entered =0;
showMainMenu =1;
}
if (val ==1)
{
csvRepStyle = false;
Serial.println ("Report style was set to normal");
set = 0;
set6Entered =0;
showMainMenu =1;
}
else if (val ==2)
{
csvRepStyle = true;
Serial.println ("Report style was set to CSV");
set = 0;
set6Entered =0;
showMainMenu =1;
}
else
{
Serial.println ("Wrong choice.");
set6Entered =0;
}
}
}
if (set ==7) //set discharge current in mA
{
if (set7Entered ==0)
{
Serial.println ("Enter discharge mode. 1 for CC ,2 for constant Power, 3 for constant Resistance");
Serial.println ("Press 0 to exit to main menu");
set7Entered =1;
}
if (Serial.available() > 0 )
{
valString = Serial.readStringUntil('\n'); //прочитать всё
int val;
Serial.println(valString);
valString.toCharArray(valChar,sizeof(valChar));
val = atof(valChar);
valString = "";
switch (val){
case 0:
set7Entered =0;
set = 0;
showMainMenu =1;
break;
case 1:
dischMode = val;
Serial.print ("Discharge mode is set to ");
Serial.println (dischMode);
set = 0;
set7Entered =0;
showMainMenu =1;
break;
case 2:
dischMode = val;
Serial.print ("Discharge mode is set to ");
Serial.println (dischMode);
set = 0;
set7Entered =0;
showMainMenu =1;
break;
case 3:
dischMode = val;
Serial.print ("Discharge mode is set to ");
Serial.println (dischMode);
set = 0;
set7Entered =0;
showMainMenu =1;
break;
default:
Serial.println ("Wrong choice.");
set7Entered =0;
}
}
}
}
int calcVoltageNew (int iternum)
{
int Max =0;
int Min = 20000;
int volt_m[iternum-1];
float volt = 0;
for (int i = 0;i<iternum;i++)
{
volt_m[i]= analogRead(VoltageSensePin);
if (volt_m[i] >Max) {Max = volt_m[i]; }
if (volt_m[i] <Min) {Min = volt_m[i]; }
delay (1);
volt+= volt_m[i];
}
volt = volt - Min;
volt = volt - Max;
//1.Если напряжение ниже (250/1023)*vref, и делитель 1:20, включаем повышенную точность (делитель 1:5) со следующей итерации.
if (div5VState == false && volt<(250 * (iternum-2)))
{
volt = volt * vref *calibrationVolt;
volt = volt/1024;
volt = volt * voltageDividerRatio ;//mV
volt = volt/(iternum-2);
max5Volt(true);
return volt;
}
//2.Если напряжение выше (1022/1023)*vref, и делитель 1:5, включаем делитель 1:20 со следующей итерации.
if (div5VState == true && volt>=(1022 * (iternum-2)))
{
volt = volt * vref *calibrationVolt;
volt = volt/1024;
volt = volt * voltageDividerRatio ;//mV
volt = volt/(iternum-2);
max5Volt(false);
return volt;
}
volt = volt * vref *calibrationVolt;
volt = volt/1024;
volt = volt * voltageDividerRatio ;//mV
volt = volt/(iternum-2);
return volt;
}
int calcCurrentNew (int iternum)
{
word Max =0;
word Min = 20000;
int volt_m[iternum-1];
float curr = 0;
for (int i = 0;i<iternum;i++)
{
volt_m[i]= analogRead(CurrentSensePin);
if (volt_m[i] >Max) {Max = volt_m[i]; }
if (volt_m[i] <Min) {Min = volt_m[i]; }
delay (1);
curr+= volt_m[i];
}
curr = curr - Min;
curr = curr - Max;
curr = curr * vref * calibrationCurr;
curr = curr / 1024;
curr = curr/ sensorResOhm ;//mA
curr = curr/(iternum-2);
curr=curr+offsetCurr;//ацп ошибается на это значение вне зависимости от тока, корректируем смещение
return curr;
}
int calcVoltageNewDAC (int iternum)
{
int Max =0;
int Min = 20000;
int volt_m[iternum-1];
float volt = 0;
for (int i = 0;i<iternum;i++)
{
volt_m[i]= analogRead(VoltageSensePin);
if (volt_m[i] >Max) {Max = volt_m[i]; }
if (volt_m[i] <Min) {Min = volt_m[i]; }
delay (1);
volt+= volt_m[i];
}
volt = volt - Min;
volt = volt - Max;
volt = volt/(iternum-2);
return volt;
}
void max5Volt (bool state)
{
if (state == true)
{
pinMode (voltDividerPin,INPUT);
voltageDividerRatio = lowDivRatio;
div5VState = true;
}
if (state ==false )
{
pinMode (voltDividerPin,OUTPUT);
digitalWrite (voltDividerPin,0);
voltageDividerRatio = highDivRatio;
div5VState = false;
}
}
+59 |
2904
75
|
+34 |
3449
49
|
Наоборот же хорошо, всё в одном месте, никуда не потеряется. Это же не гигабайтное видео.
Нагрузка у вас получилась отличная. Уверен, что будут желающие повторить её и через месяц и через год-два. Вот только на github'е исходники сохранятся, а на других ресурсах есть огромный шанс того, что они потеряются.
Было бы очень классно, если бы вы выложили исходники на github вместе со схемами и картинками.
И это я ещё молчу про потенциальные PR'ы.
Вмемориз.
ЗЫ: Прошу заметить, что вариант держателя «Изначально использовал» принципиально не подходит для защищенных АБ.
Про автозамену кавычек на сайте не знал.
Положил под тег «код», проверил, компилируется.
Спасибо, что заметили!
А вообще проводил измерения емкости 4-х аккумуляторов в 4-х приборах. Отклонение в емкости от 0,07 до 3,07% Есть готовая таблица, но здесь ее не показать.
2. вам ничего не мешает измерять напряжение не только на верхнем конце R2, но и на его нижнем конце. Частично выправится малотоковый диапазон.
3. в PD верните назад i.
4. Как и «Измерения напряжения и тока пятикратное» — совсем не достижение. Их надо измерять постоянно. Знаете, зачем делается oversampling ADC?
2. Попробую.
3. Пробовал, пока с i только хуже, очевидно, коэффициент не подобрал. На серьёзный, правильный рассчет pid коэффициентов пока морально не готов.
4. Странная формулировка про достижение. Я описываю, как работает. Оверсэмплинг увеличивает точность-разрядность измерений, но, насколько я понимаю, надо измерять сотни раз, чтобы получить бОльшую точность на моей скорости измерения. Я пока буду сэмплить, фет ток уведёт куда-нибудь.
Мы же не температуру слона измеряем. Что Вы имеете в виду под «постоянно»? Я и так их постоянно измеряю, сто раз в секунду, и фетом рулю столько же.
А можно ли использовать для улучшения детализации не встроенный ацп ардуино, а схемы типа INA219 | INA 226 — и как с ними тогда поменяется схема?
Дальше снимаете с дачика данные тока и напряжения и на их основе вычисляете сигнал для транзистора.
Автор пошёл своим путём, скорее всего для максимального упрощения, но на мой взгляд это слишком уж упрощено.
Можно было использовать схему от gandf, но со своим ПО и возможно добавить режим cv
Кроме того, с шунта напряжение измеряется вторым ОУ, с изменяемым коэфициентом усиления, вот собственно вся основная схема.
При этом
1. Нет зависимости тока и режима работы от примененного транзистора
2. Больше температурная стабильность
3. Аппаратная ОС
4. Коррекция тока по измеренному значению.
Т.е. по сути к схеме из этой статьи добавлено 2 ОУ и немного мелочевки.
Если нагрузку из этого поста я повторить ещё осилю, то нагрузка от gandf для меня уже за пределами возможностей. Тогда уж лучше купить готовый Atorch DL24.
Полностью согласен, это хороший вариант. Мне же как раз больше понравилась работа М8 от Gandf, хотя она гораздо проще функциональна, но здесь больше влияет специфика применения.
Первую собрал самостоятельно вообще неподготовленный человек.
Ацп у ардуины не точное, конечно сравнение с референсом в ардуине хорошо, но он к сожалению тоже сильно гуляет.
Спишем на придирку.
Но это, это бред к сожалению.
Встроенный кварц у ардуины вообще 0, внешний не сильно лучше, время там скачет сильнее курса рубля, и секунда может быть вполне посчитана как 0.8. А еще есть варианты упереться в точность вычисления на ардуине тк она тоже далеко не идеальна, и показатели этого тестера можно принимать как очень примерные.
Те то что там 2000mAh а не 1000 понятно, но если в комнате меняется температура а питание не сильно стабильное, то +-300мАч на такой схеме можно получать без проблем.
Если дорабатывать именно эту плату то при измерении аккумов можно вообще убрать резистивный делитель, те ацп у нас 5в ( для 5в атмеги, и мы так и так не выйдем за рамки).
Если же хотим чуть больше, то лучше нормально посчитать, ибо в версии автора там диапазон примерно до 24-25в и измеряя 12в мы теряем в точности которой у нас и так не много (ага, 0.02в* на огромный шум*на прогулку Vref На луну).
Но реально есть смысл брать сразу INA3221 ибо кроме повышенной точности там есть кучка fancy pancy фильтров, защит от уплывания ацп итд.
Но больше косяков конечно вносит подсчет времени который крив. В идеале скинуть работу на таймер и вызывать код подсчета именно по этим дискретным чисам, модулей для дуины много, убегают они 0.5-1мин в месяц, ну или 2.5сек в день, или 0.1сек в час, те раз в 50 меньше чем ардуина может в стоке.
И получить ограничение при измерении напряжения до 26 вольт, реально этого мало, разве что только для небольших сборок. Именно из-за подобного ограничения лично мне не нравятся нагрузки ZKE.
Хотя сама идея использования отдельного АЦП однозначно хорошая.
Это касается именно ардуины? Я всегда думал что обычно там пара кондеров+кварц и накосячить очень сложно. Да и тесты обычно идут не более 5 часов, нормального кварца хватает с головой.
Обычно достаточно сделать переключение диапазонов, например до 5 вольт и более, так как высокая точность нужна обычно именно при низких напряжениях.
И в итоге будет еще хуже, сегодня надо проверить одну ячейку, а завтра попался аккумулятор 8.4 вольта и все?
У автора проблема не в кварцах и АЦП, а в примитивном управлении транзистором и большой зависимости от внешних факторов. Там такой разбег будет, что уход кварца это последнее о чем бы я думал.
Это как?
По-вашему, вычисления в unsigned long в Arduino имеют бОльшую погрешность, чем вычисления в unsigned long на мощном десктопе?
Это же понятно, что данная нагрузка скорее эксперимент с ардуиной и пригодится разово потестировать аккумуляторы «для фонарика» простому обывателю. Простая конструкция, быть может, повторив которую, кому-то не придётся покупать электронную нагрузку, которая потом будет пылиться в шкафу.
все уже сделано
www.radiokot.ru/forum/viewtopic.php?f=11&t=138699
Плюнул. Будет надо — сделаю свое. С блютузом и лабвью. По крайней мере, ошибки в ней будут «мои».
Оценить качество схемотехническое части не могу, но за стиль оформления поста (нет даже схемы с картинкой, чтобы с первого взгляда оценить «а смогу ли я это хотябы попробовать сделать?») и качество ответов автора того поста твёрдая двойка.
Особенно порадовало, что в одном месте посылает «а вот пусть читают те дофига страниц по ссылке», а сразу же рядом «а это мне было лень на схеме прорисовывать» и «а тут я слепил из того, что попалось под руку и такие цифры на схеме прописал, можете ставить что угодно».
Т.е. схема явно не уровня «взял и повторил», а «вот вам, профессионалам, заготовка, там местами косяки, но вы же профессионалы, сможете в процессе реализации их сами исправить, можете даже что-то улучшить».
Так что полностью поддерживаю.
Я бы еще подключил ее между стоком полевика и аккумом, а то этот шунт является ООС и призакрывает полевик, уменьшая мах ток.
Спасибо за быстрый ответ, имел ввиду готовый модуль, вечером попробую сеобрать, а то у меня как раз нагрузка навернулась.А тут такой вариант как раз для такого чайника как я.Да ещё попробую к ней экран прикрутить 16 2, шоб без компа.
Программная ООС по току всё-же имеет право на жизнь, несмотря на множество недостатков.
(Делал, даже на stm32 выходит погано, но там это было вынужденно. Впрочем, и там пришлось таки поставить nсp1450, в ущерб пмехам.)
Почему бы при разрядке не перегонять энергию в другие аккумуляторы?
Я понимаю что 0,01кВт*ч с одного аккума это мизер, но при массовых тестах мы имеем более солидные цифры и обильное тепловыделение
Мало того, есть и обычные электронные нагрузки, в смысле не только для тестов аккумуляторов, которые имеют функцию рекуперации в сеть.
Но в первом случае можно случайно упереться в то, что аккумулятор который работает как нагрузка, уже зарядился, а тест надо продолжать, да и зачем нужен дополнительный износ аккумуляторов.
Во втором случае цена, такие устройства дороже.
Кроме того это усложняет конструкцию.
А куда потом девать заряженные аккумуяторы при массовых тестах, разряжать другой нагрузкой? :)
Но это фигня, бредит проект для моторчика под параплан со смешной мощностью и не менее смешной нагрузкой на батарейки. Плюс «нагрузка» (механическая) на этот моторчик. Поэтому прорабатывается вариант с удвоенным количеством моторов (наша разработка — мотор) по концепту мотор-генератор. Но и это мелочи. Моторчик только демашка для натурных испытаний. Одна пятая нормальной версии. Впрочем, вряд-ли будет сделано.
Так что, возврат энергии на высокую часть — вполне нормальное решение. Да и не особо сложное.
Все относительно…
Зимой вообще тепло не пропадает, обогревает помещение.
Сравнивать, действительно, не совсем верно, но нужно было хоть примерно прикинуть.
Теперь представьте, что у каждого провода есть сопротивление.
Вы прицепили делитель напряжения таким образом, что включили сопротивление провода от плюсового вывода аккумулятора до прехохрвнителя., похоронив тем самым преимещуства трёхпроводной схемы измерения. Также вы вносите некую неопределённость, куда подключать землю ардуино. На моей схеме явно указано что куда, а по Вашей версии схемы есть шанс включить ардуино таким образом, что часть сопротивление провода от шунта до ардуино будет являться шуном.
Минимальный ток тоже можно раз в 10 снизить, если сделать управляемый полевым транзистором шунт. По аналогии с R5 только для резистора токового шунта. Один низкоомный через полевик подключается, высокоомный остается включенным всегда. Но это усложнение небольшое.
В тексте Вы писали:
Какие размышления на этот счет, принципиальная схема?
Понять сразу алгоритм работы очень сложно.
" скомпилировал код в IDE версии, 1.6.9 компилит нормально. Свежие версии собирают не правильно. Что-то в компиляторе изменили…"
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.