Простая электронная нагрузка на Arduino.

Однажды захотелось провести ревизию своих аккумуляторов разной степени убитости.
А ещё чесались руки покодить на Arduino, и вот что из этого вышло.


Всем привет.
Предлагаю вашему вниманию простую схему для тестирования ёмкости аккумуляторов током до 5 А с рабочим напряжением до 20 В.

На муське уже был DIY обзор вполне годной самодельной электронной нагрузки.
В комментариях обсуждали плюсы и минусы данной схемы, а также мысли о программном поддержании тока разряда самой Ардуино.
За основу взял схему товарища Ksiman

Операционных усилителей в наличии не было, пришлось обходиться без них:


Как видите, в железе она реально простая.
На схеме видим аккумулятор в трёхпроводной схеме подключения, который будет разряжаться через датчик тока R2.
Нога currentSense мониторит ток. Для большей точности АЦП опорное напряжение выбрано внутреннее (1.1 вольт для atmega328). Теоретическая точность поддержания тока – 10ма.
Схема также мониторит напряжение на аккумуляторе, подавая его через делитель 1/5 или 1/20 на ногу VSense.
Делитель работает в двух режимах – если напряжение аккумулятора выше 5В, нога «1/5-1/20» замыкается на землю, получаем делитель 1 к 20.
Разрядность измерения напряжения в таком случае около 20 мВ (1100мВ опорного ардуино/1024 шага) * коэффициент деления 20
Если напряжение аккумулятора меньше 5В, ардуино подвешивает ногу «1/5-1/20» в высокоимпедансное состояние, резистор R5 оказывается подвешенным в воздухе и в делении не участвует – получается делитель 1 к 5 и большая точность измерения напряжения. Спасибо товарищу Ksiman за идею.

Принцип работы весьма прост: через «интерфейс» (монитор com порта) задаём ток разряда в миллиамперах, а также напряжение отсечки в милливольтах, и нажимаем запуск.
В этот момент запускается PD регулятор, который в цикле несколько десятков раз в секунду отслеживает ток на датчике тока R2, сравнивает его с целевым значением и подаёт ШИМ 5В (нога PWM) на интегратор R1C1, увеличивая или уменьшая ток нагрузки.
Напряжение на затворе меняется очень плавно (постоянная времени R1C1 около 0.5с), за счёт этого обеспечивается хорошая точность поддержания тока несмотря на весьма посредственную линейность полевика в линейном режиме.
Частота ШИМ 2кГц, разрядность 10 бит.
Измерения напряжения и тока пятикратное, максимальное и минимальное значения выкидываются, оставшиеся 3 усредняются.

Использование

Заливаем скетч в ардуино (я использовал nano).
Настраиваем (раздел по настройке чуть ниже).
Идём в монитор com-порта:
Я немного заигрался, и получилась вот такая большая менюха.
Здесь отображаются текущие показания вольтметра и амперметра, целевой ток и напряжение, а также меню запуска и настройки.
Нагрузка показывает 4159 мВ, реальное напряжение на батарее при этом 4.16 В

Выбираем нужный режим, напряжение отсечки, ток и запускаем процесс.
Управление подробно
Нажимаем 3, вводим значение напряжение отсечки:
Я выбрал 3000 мВ. Новые показания отобразились на панели управления.

Устанавливаем ток разряда.
Нажимаем 2, вводим ток в миллиамперах, например 3500

Нажимаем 1, процесс запускается.
Перед запуском программа проверяет наличие аккумулятора – если напряжение ниже 500мВ, то процесс не запустится, высветится сообщение.
До нуля при, этом, разрядить аккумулятор можно, выставив соответствующий Cut-off через меню.
В любой момент в процессе разрядки, как и в любом пункте меню, можно прерваться, отправив 0

Как видим, ток не достиг целевого значения, напряжения на затворе не хватило, чтобы транзистор нормально открылся. Тут всему виной напряжение USB конструктивная особенность платы nano: у неё по входу питания от USB стоит диод, после которого в питание приходит 4.6 вольта. Расчёт всё равно получится корректным, т.к. для расчёта используются реальные показания тока. Но мы для красоты выставим ток 3А :-)

При показаниях 3.910 мВ реальное напряжение на клеммах батареи 3.93В, а относительно нуля ардуины – всё те же 3.91В.
Разница падает на сопротивлении клеммника и минусового отрезка провода от держалки батареи до общей точки — плата за трёхпроводную схему измерения. Да и сама держалка имеет контакты слишком мелкого диаметра, аккумулятор то и дело норовит коснуться лишь части поверхности электрода держалки, что тоже увеличивает сопротивление.
По достижении заданного напряжения или по нажатию 0 разряд прекращается.

остальные пункты меню
Пункт 4 меню для дебага – просто показывает показания АЦП. Нажать 0 для выхода в главное меню.
Пункт 5 слегка приоткрывает транзистор, не используя PD-регулятор, чтобы можно было померить ток амперметром. Был нужен для дебага, пригодится для калибровки. Нажать 0 для выхода в главное меню.
Пункт 6 позволяет переключать стиль отображения информации CSV или «нормальный»
CSV режим – удобен для отправки данных в эксель.
«Нормальный» режим:

Power – это на самом деле не мощность, а текущее значение ШИМ (1023 максимум, т.е. 5 вольт)
Calcs per interval говорит нам о том, сколько раз PD регулятор поработал за время между отправкой данных в com-порт (2 секунды по умолчанию).
Пока писал обзор, дописал и протестировал пункт 7 — переключение между режимами разряда постоянным током, сопротивлением или мощностью.



Получившиеся данные с помощью технологии CopyPaste копируем в Excel и строим графики.
Относительно свеженькая VTC5, куплена около года назад, использовалась раз 30.

А вот этим двум товарищам явно пора на покой, в процессе разряда нагрелись до 42 и 37 градусов, отдав смешные 1300 и 1670 мАч. Это и не удивительно, я 3 года практически ежедневно разряжал их «досуха» в мощной электронной сигарете.

А вот 18650 элемент с интригующим названием SkywolEye на токе 1 А выдал потрясающие 69мАч.

Уже в процессе написания данной статьи, хохмы ради прикрутил режим разряда постоянным сопротивлением и постоянной мощностью:


Поговорим о точности измерений

В процессе тестирования замерял напряжение и ток самыми простыми мультиметрами, для контроля сверяясь с осциллографом. Результаты свёл в таблицу.

Как видим, на токах до 200 мА ловить нечего – практически постоянная ошибка в 25-30 мА.
Ток при этом стабильный, но неправильный. Можно было откалибровать программно, но это не спортивно, у нас всё-таки простая электронная нагрузка.
Начиная от 2,5 Ампер ошибка вызвана разогревом шунта, при обдуве его вентилятором схема выходит на заданный ток.
Последняя строка, выделенная синим — максимальный ток, который удалось получить с 5 вольт управляющего напряжения. Здесь использовал Arduino UNO — у неё нет диода по входу USB, и транзистор открывается лучше.
Погрешность по току стала меньше, т.к. здесь использовал маленький чит в виде обдува шунта. Будем считать, что я сделал шунт помощнее :-)

С напряжением дела обстоят получше, погрешность небольшая, растёт с током, вызвана, очевидно, сопротивлениями силовых контактов.
Что касается измерения энергии:
Протестированная мной VTC5 по данным прибора имеет заряд в 2449 мАч и энергию в 8618 мВтч.
Из случайных источников взял энергию данного элемента и сравнил с тем, что у меня получилось.
Источник 1
Источник 2
Видим погрешность измерения заряда («ёмкости») и энергии 2% и 4%, соответственно.
Следует также учесть тот факт, что элемент у меня не новый и его запасённая энергия меньше.
Если пересчитать полученную мной энергию с учётом погрешности измерений напряжения в 33мВ (т.е. «увеличить» напряжение в расчёте на эти 33 мВ), то отданная энергия получается 8703 мВтч, что на 1 % больше посчитанного ардуиной значения.

А давайте посмотрим сигналы

На затворе полевика всё ровно и гладко, давайте покажу сигнал на токовом шунте.
В открытом режиме осциллографа пульсации тока не видны, показываю в закрытом режиме (это когда постоянная составляющая сигнала отфильтровывается) на пределе 5 мв/дел, т.е. 1 клетка это 50 мА тока в нагрузке.
Осциллограмма меня не порадовала, была видна какая-то широченная помеха шириной аж в 2 клетки.

В одной клетке 200нс умещаются аж 3 периода, нетрудно посчитать период сигнала в 66, (6) нс и частоту в 15 мгц, что подозрительно похоже на частоту кварца Ардуино.
Как оказалось, ардуино довольно шумная и излучает вокруг себя во все стороны радиопомеху на своей тактовой частоте. Даже с щупом, замкнутым на собственный земляной крокодильчик, эта помеха имела место. Даже с выключенной электронной нагрузкой. Даже просто на голой ардуине, к которой подведено только питание. К счастью, помеха не имеет видимого влияния на стабилизацию тока, т.к. довольно симметричная и быстрая.
Однако в процессе разряда аккумуляторов ток всё же немного «дрожит», примерно на 1/10 ячейки, или 5 мА.
Выглядит вот так.

Мультиметр на «точном» пределе в 200ма такие пульсации практически не замечает, по его показаниям ток меняется на 1-2 мА.

Выбор компонентов и рекомендации

Держалка батарей
Изначально использовал вот такую , припаяв 3 провода для трехпроводного включения. Её вполне можно использовать на обозреваемых «детских» токах.
Потом подоспела вот такая
За свои деньги неплохой «любительский» вариант, удобна универсальностью, хотя я почти уверен, что на высоких токах будет давать сильную погрешность.
Силовой транзистор подойдёт в принципе любой, типа IRP460, IRFP260, irfp250, w20nk50z и им подобные. Не стоит гнаться за большими токами и напряжениями в даташитах, также как и за маленьким сопротивлением Rds(on) – всё равно транзистор работает в линейном режиме, т.е. он своим сопротивлением регулирует ток в нагрузке.
В первом приближении размер корпуса транзистора намного важнее тока и напряжения. Больше корпус – больше сможет рассеять. Одну банку 18650 на токе в 4 ампера можно и на irf840 разрядить. Я на всякий случай прицепил транзистор на винтовой клеммник для оперативной его замены в случае отстрела :-)
Я также экспериментировал с безымянными IGBT в TO-247 из плазменного телевизора, они классные, но за счёт падения напряжения эмиттер-коллектор одиночные ni-mh не потестировать.
Токовый шунт необходимо выбирать с запасом по мощности, чтобы не нагревался, либо искать резисторы, выполненные из материалов с низким температурным коэффициентом сопротивления. У меня валялись выдранные откуда-то пятиваттные MPR-5W, собрал 3 шт последовательно, на токе 5А греются до 50 градусов. Судя по показаниям старенького MAS830, на 3 амперах ток разряда в работе уплывает на 20 мА из-за нагрева резисторов, т.е. сборочку резисторов надо бы делать помощнее.

Минусовой провод от батареи до общей на схеме точки делаем максимально толстым и коротким – на нём просаживается часть напряжения, что несколько ухудшает точность замера напряжения на АКБ по сравнению с классической 4-х проводной схемой. Я не заморачивался и взял чёрный провод от БП ATX.
Мой вариант выполнен в классическом раздолбайско-макетном стиле

Настройка скетча
В начале скетча прописаны и прокомментированы все параметры, в том числе какие пины ардуино задействованы. Все настройки собраны в первых 30 строках программы.
Из важного –
необходимо перевести ардуино на внутреннее опорное напряжение (можно просто залить описываемый скетч) и измерить напряжение между ref и землёй Ардуино, подставить значение в милливольтах в переменную vref
Также необходимо настроить ваши коэффициенты под «точный» и «неточный» делители напряжения.
Ну и пару раз замерить ток и напряжение мультиметром и сравнить с показаниями ардуино, дабы в программе скорректировать сопротивление шунта и коэффициенты деления, чтобы показания нашего прибора стали верными.

Программа

// 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;
  }
  }


Выводы и результаты


схема простая, работает, но не без косяков.
Плюсы данного девайса:
• простота сборки, повторяемость: сможет собрать и настроить любой, у кого есть Ардуино, мультиметр и горстка доступных радиокомпонентов.
• Приемлемая точность измерения ёмкости (заряда) в диапазоне от 500 мА.
• Несколько режимов измерения.
За простоту приходится расплачиваться:
• Минимальный ток разряда 150мА
• Завышает ток на 10-20 мА на низких токах разряда (до 500 мА).
• сложность масштабирования (необходимо городить дополнительные PD контроллеры под каждый дополнительный мосфет)
• плохая точность расчёта запасённой энергии.
Надеюсь, данная заготовка пригодится тем, кто хотел потестировать свои аккумуляторы в лёгком режиме, но по каким-то причинам стеснялся. Точности в 2% для измерения ёмкости должно хватить.
В процессе макетирования родилось много идей и задумок, но я сегодня решил всё же не отходить от темы электронной нагрузки из минимума деталей.
Например, сюда можно приделать схему заряда, датчик температуры батарей и выводить её в график, или аварийно останавливать разряд по температуре.
Чтобы увеличить точность, всё же не обойтись без ОУ: необходимо усиливать сигнал с шунта с помощью ОУ и, соответственно, повышать опорное, например, брать его с той же TL431.

Конструктивные комментарии по проведённой работе приветствуются.

Надеюсь, было интересно. Спасибо за внимание.
Добавить в избранное +158 +227
+
avatar
  • ABATAPA
  • 06 января 2021, 19:30
+2
Программу хорошо бы на pastebin или подобное, тут больно большая простыня, хоть и под спойлером…
+
avatar
0
Ну или в облаке где-то расшарить…
+
avatar
  • diz
  • 06 января 2021, 21:24
+3
github
+
avatar
  • BSP
  • 06 января 2021, 21:28
+65
А чего Вам всем не нравится то? Ну я могу написать в спойлере «осторожно, месиво на тыщу строк», но ведь можно просто закрыть спойлер обратно :-)
Наоборот же хорошо, всё в одном месте, никуда не потеряется. Это же не гигабайтное видео.
+
avatar
  • diz
  • 06 января 2021, 22:43
+3
мне всё нравится. Я просто предложил вариант, куда можно и, по идее, нужно, заливать сорцы. А не на всякие пастебины, где через год всё протухнет и сомнительное облако, которое тоже может протухнуть
+
avatar
  • vp7
  • 06 января 2021, 23:16
+7
Поддержу.
Нагрузка у вас получилась отличная. Уверен, что будут желающие повторить её и через месяц и через год-два. Вот только на github'е исходники сохранятся, а на других ресурсах есть огромный шанс того, что они потеряются.

Было бы очень классно, если бы вы выложили исходники на github вместе со схемами и картинками.
+
avatar
  • nochkin
  • 07 января 2021, 07:19
+8
На Github'е они не только сохраняться, но можно делать изменения кода с сохранением истории. И не надо потом помнить на каких сайтах и где надо обновлять эту же простыню.
И это я ещё молчу про потенциальные PR'ы.
+
avatar
+6
Вот такие штуки на сайте реально полезны!
Вмемориз.

ЗЫ: Прошу заметить, что вариант держателя «Изначально использовал» принципиально не подходит для защищенных АБ.
+
avatar
  • troebel
  • 06 января 2021, 19:42
+8
Я бы еще добавил, что в даташите, при выборе транзистора, полезно смотреть на «safe area» и есть ли там в принципе линейный режим, ну и тепловые сопротивления чип-корпус и корпус-радиатор, пока так выходит, что с ТО220 сдуть больше 30-40Вт не выйдет, а просто огромный ТО247 упрется примерно в 150-200Вт.
+
avatar
+7
Скобки IDE не принимает, («Discharge mode is set to „); и нижние ковычки тоже. Можно конечно исправить пару сотен строк на " ", но муторно. Неужели нельзя было по нормальному программу залить?
+
avatar
  • BSP
  • 07 января 2021, 09:41
+7
Прошу прощения, первый раз пишу.
Про автозамену кавычек на сайте не знал.
Положил под тег «код», проверил, компилируется.
Спасибо, что заметили!
+
avatar
  • mfiless
  • 06 января 2021, 20:06
+1
Аккумуляторы дело мутное. Один и тот же аккумулятор. Условия заряда и разряда одинаковые. Тест емкости неделю назад и сегодня. Разница в емкости 0,52% Точность нагрузки 0,05%
А вообще проводил измерения емкости 4-х аккумуляторов в 4-х приборах. Отклонение в емкости от 0,07 до 3,07% Есть готовая таблица, но здесь ее не показать.
+
avatar
  • ABATAPA
  • 06 января 2021, 20:11
+4
Разница в емкости 0,52% Точность нагрузки 0,05%
Вы или "%" уберите и оперируйте долями, или размерность скорректируйте.
+
avatar
  • u3712
  • 06 января 2021, 20:09
+12
1. между конденсатором и затвором надо ставить резистор. Ключевое слово «надо».
2. вам ничего не мешает измерять напряжение не только на верхнем конце R2, но и на его нижнем конце. Частично выправится малотоковый диапазон.
3. в PD верните назад i.
4. Как и «Измерения напряжения и тока пятикратное» — совсем не достижение. Их надо измерять постоянно. Знаете, зачем делается oversampling ADC?
+
avatar
  • BSP
  • 06 января 2021, 20:41
+2
1. Позвольте полюбопытствовать, зачем он там.
2. Попробую.
3. Пробовал, пока с i только хуже, очевидно, коэффициент не подобрал. На серьёзный, правильный рассчет pid коэффициентов пока морально не готов.
4. Странная формулировка про достижение. Я описываю, как работает. Оверсэмплинг увеличивает точность-разрядность измерений, но, насколько я понимаю, надо измерять сотни раз, чтобы получить бОльшую точность на моей скорости измерения. Я пока буду сэмплить, фет ток уведёт куда-нибудь.
Мы же не температуру слона измеряем. Что Вы имеете в виду под «постоянно»? Я и так их постоянно измеряю, сто раз в секунду, и фетом рулю столько же.
+
avatar
  • u3712
  • 06 января 2021, 21:27
0
+
avatar
  • BSP
  • 07 января 2021, 08:32
+5
Если Вы имеете в виду звон в затворе, то здесь ему взяться особо неоткуда, на затворе огромная емкость висит, между ардуиной и транзистором резистор на 10к. Самоуспокоения ради можно добавить между c1 и транзистором резистор на 1-10 ом, хуже не будет. Тут в схему много чего можно добавить для улучшения, но изначальная фантазия была — минимализм в железе)
+
avatar
+3
Спасибо, надо пробовать повторить.
А можно ли использовать для улучшения детализации не встроенный ацп ардуино, а схемы типа INA219 | INA 226 — и как с ними тогда поменяется схема?
+
avatar
  • koticik
  • 06 января 2021, 21:13
+2
По идее выбрасываете делитель напряжения, и ставите свой датчик в + или — аккумулятора, соответсвенно либо перед транзистором, либо после него. Линия измерения тока от шунта Вам тоже не нужна.
Дальше снимаете с дачика данные тока и напряжения и на их основе вычисляете сигнал для транзистора.
+
avatar
+6
Защита ноги измерения напряжения нужна, хотя бы стабилитрон повесить
+
avatar
  • Omega
  • 06 января 2021, 21:34
+1
Интересно, что скажет kirich
+
avatar
  • kirich
  • 06 января 2021, 22:02
+2
А что тут говорить, есть хорошая и простая для повторения схема от gandf, для большинства задач хватает. Причём она лучше аппаратно и есть её исходники.
Автор пошёл своим путём, скорее всего для максимального упрощения, но на мой взгляд это слишком уж упрощено.

Можно было использовать схему от gandf, но со своим ПО и возможно добавить режим cv
+
avatar
0
А можно ссылку от gandf или это то что вы собирали здесь?
+
avatar
  • kirich
  • 06 января 2021, 23:54
+3
Да, это эта нагрузка.
+
avatar
+10
по сравнению с пятью детальками выглядит как тёмный лес, требующий доработки и отсутствующий в продаже…
+
avatar
  • kirich
  • 07 января 2021, 00:15
+1
Это только кажется на первый взгляд, принцип предельно прост, стоит микроконтроллер, к его выходу с ШИМ прицеплена интегрирующая цепочка, далее делитель, с выхода делителя получаем напряжение 0-250мВ, которое подаем на сравнивающий ОУ к выходу которого прицеплен полевик.
Кроме того, с шунта напряжение измеряется вторым ОУ, с изменяемым коэфициентом усиления, вот собственно вся основная схема.

При этом
1. Нет зависимости тока и режима работы от примененного транзистора
2. Больше температурная стабильность
3. Аппаратная ОС
4. Коррекция тока по измеренному значению.

Т.е. по сути к схеме из этой статьи добавлено 2 ОУ и немного мелочевки.
+
avatar
  • vp7
  • 07 января 2021, 02:00
+10
Это только кажется на первый взгляд
Для несведующих в электронике людей — и на второй взгляд тоже.
Если нагрузку из этого поста я повторить ещё осилю, то нагрузка от gandf для меня уже за пределами возможностей. Тогда уж лучше купить готовый Atorch DL24.
+
avatar
  • kirich
  • 07 января 2021, 02:14
+4
Ну вообще изначально мой пост был по поводу статьи, автор которой вполне разбирается в электронике.

Тогда уж лучше купить готовый Atorch DL24
Полностью согласен, это хороший вариант. Мне же как раз больше понравилась работа М8 от Gandf, хотя она гораздо проще функциональна, но здесь больше влияет специфика применения.
+
avatar
  • kirich
  • 07 января 2021, 02:36
+1
Кстати, вполне можно было взять за базу для экспериментов эту нагрузку или конструктор, по сути ведь надо ШИМом формировать напряжение в определенном диапазоне, а дальше дело техники.
Первую собрал самостоятельно вообще неподготовленный человек.
+
avatar
  • SOLLUX
  • 07 января 2021, 04:05
+1
вполне можно было взять за базу для экспериментов эту нагрузку или конструктор
Воот, это дело, тем более многие муськовчане собрали нагрузку или купили конструктор.
+
avatar
  • tirarex
  • 06 января 2021, 22:52
+3
Интересно, что скажет kirich
Ничего путного не сказал.

Ацп у ардуины не точное, конечно сравнение с референсом в ардуине хорошо, но он к сожалению тоже сильно гуляет.
Спишем на придирку.

Но это, это бред к сожалению.
Встроенный кварц у ардуины вообще 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 меньше чем ардуина может в стоке.
+
avatar
  • kirich
  • 07 января 2021, 00:00
+5
Ничего путного не сказал.
по программе я ничего бы и не сказал, потому как вообще не моё, а насчет аппаратного моего комментария вполне достаточно.

Но реально есть смысл брать сразу INA3221
И получить ограничение при измерении напряжения до 26 вольт, реально этого мало, разве что только для небольших сборок. Именно из-за подобного ограничения лично мне не нравятся нагрузки ZKE.
Хотя сама идея использования отдельного АЦП однозначно хорошая.

Встроенный кварц у ардуины вообще 0, внешний не сильно лучше
Это касается именно ардуины? Я всегда думал что обычно там пара кондеров+кварц и накосячить очень сложно. Да и тесты обычно идут не более 5 часов, нормального кварца хватает с головой.

Обычно достаточно сделать переключение диапазонов, например до 5 вольт и более, так как высокая точность нужна обычно именно при низких напряжениях.

то при измерении аккумов можно вообще убрать резистивный делитель
И в итоге будет еще хуже, сегодня надо проверить одну ячейку, а завтра попался аккумулятор 8.4 вольта и все?

У автора проблема не в кварцах и АЦП, а в примитивном управлении транзистором и большой зависимости от внешних факторов. Там такой разбег будет, что уход кварца это последнее о чем бы я думал.
+
avatar
+1
Встроенный кварц у ардуины вообще 0, внешний не сильно лучше, время там скачет сильнее курса рубля, и секунда может быть вполне посчитана как 0.8.
Что за «встроенный» кварц? RC-генератор атмеги? Так его никому в голову не придет использовать там, где нужна какая-либо точность по времени. А с внешним кварцем все в порядке, как раз. Обычная точность кварца на уровне килогерц. Никаких «0.8 секунды» там и нет близко. Может, вам попадались проекты с программными ошибками? В атмеге нет регистра, считающего такты, надо использовать таймеры, а время увеличивать по прерываниям от них. А прерывания могут запрещаться, в результате чего программа считает уже не то.
+
avatar
  • BSP
  • 07 января 2021, 09:58
+8
Интересно, что скажет kirich
Удивительно, что он вообще что-то сказал, с учётом его специфики (электроник) и уровня его измерительной техники.
Это же понятно, что данная нагрузка скорее эксперимент с ардуиной и пригодится разово потестировать аккумуляторы «для фонарика» простому обывателю. Простая конструкция, быть может, повторив которую, кому-то не придётся покупать электронную нагрузку, которая потом будет пылиться в шкафу.
+
avatar
  • SOLLUX
  • 08 января 2021, 00:33
0
ksiman же вроде электроник :).
+
avatar
+1
жесть.
все уже сделано
www.radiokot.ru/forum/viewtopic.php?f=11&t=138699
+
avatar
+8
«Неподготовленному» пользователю сложно оценить ввиду наличия отсутствия готовой схемы и описания «в картинках и тексте» одним постом без влезания в 36 страниц комментариев
+
avatar
  • u3712
  • 07 января 2021, 00:05
+1
Возникла точно такая-же мысль.
Плюнул. Будет надо — сделаю свое. С блютузом и лабвью. По крайней мере, ошибки в ней будут «мои».
+
avatar
  • Passers
  • 07 января 2021, 12:01
0
Схема, описание, печатка и всё остальное есть в одном! архиве, в шапке темы, которую автор обновляет. Но для" неподготовленных пользователей" это действительно сложно…
+
avatar
+19
Да, сперва прокачай свой скил в познаниях электроники… Ведь подобные проекты на котах только для тех кто в теме и даже мыслей не должно быть о сборке, если ты не про уровня…
+
avatar
  • Passers
  • 07 января 2021, 12:06
+1
Не только, для начинающих — на коте есть целая ветка ссылка
+
avatar
+1
Постарел, Старичок, видимо совсем… Раньше давал ответы с разъяснениями. А вообще, очень грамотный человек.
+
avatar
  • vp7
  • 07 января 2021, 11:42
+12
Пробовал прочитать тему.

Оценить качество схемотехническое части не могу, но за стиль оформления поста (нет даже схемы с картинкой, чтобы с первого взгляда оценить «а смогу ли я это хотябы попробовать сделать?») и качество ответов автора того поста твёрдая двойка.
Особенно порадовало, что в одном месте посылает «а вот пусть читают те дофига страниц по ссылке», а сразу же рядом «а это мне было лень на схеме прорисовывать» и «а тут я слепил из того, что попалось под руку и такие цифры на схеме прописал, можете ставить что угодно».

Т.е. схема явно не уровня «взял и повторил», а «вот вам, профессионалам, заготовка, там местами косяки, но вы же профессионалы, сможете в процессе реализации их сами исправить, можете даже что-то улучшить».
+
avatar
  • kirich
  • 07 января 2021, 12:04
+7
за стиль оформления поста (нет даже схемы с картинкой, чтобы с первого взгляда оценить «а смогу ли я это хотябы попробовать сделать?») и качество ответов автора того поста твёрдая двойка.
Я вчера кстати тоже пытался читать эту тему, понял что была тема до неё, начал читать ту, в итоге скатился до просто листания так как выцепить что-то полезное и информативное в куче сообщений и обсуждений, крайне тяжело.
Так что полностью поддерживаю.
+
avatar
  • russsx
  • 07 января 2021, 03:43
-2
Статейка опоздала на лет 5. Сейчас надо было делать на stm32 минимум. Но чтоб наверстать, надо уже начинать делать нагрузку на i7
+
avatar
  • BSP
  • 07 января 2021, 10:57
+7
Квантовые компьютеры не за горами, я пока подожду.
+
avatar
0
я так понимаю, если поставить ina219, то и шунт можно выкинуть?
+
avatar
0
если поставить ina219
Это смотря что вы подразумеваете под «ina219», если отдельную мс, то вряд ли, если модуль на ней с шунтом, то да.
Я бы еще подключил ее между стоком полевика и аккумом, а то этот шунт является ООС и призакрывает полевик, уменьшая мах ток.
+
avatar
0
а то этот шунт является ООС и призакрывает полевик
Хотя, это делает из него генератор тока. Так что, можно оставить как есть. :)
+
avatar
  • koticik
  • 07 января 2021, 15:44
0
А по идее если есть внешний ацп и датчик тока то как вариант можно попробовать для больших токов что бы не мучить транзистор поставить пару резисторов керамических и второй транзистор в ключевом режиме и сгонять ток на нагрев резюков. А ещё и кулер через dc-dc на это все дело повесить и питать все с того же аккума. А ещё терморезистор для контроля температуры :)
+
avatar
  • Hector
  • 07 января 2021, 10:51
+4
Если вам нужно знать ёмкость аккумулятора и не нужен график его разряда, то вполне хватит и такой приблуды: a.aliexpress.com/_ALrJR9
+
avatar
  • Prays
  • 07 января 2021, 11:39
+2
То, что все упрощено и сделано на ардуине, а не stm, считаю плюсом. Имеет низкой порог для входа в тему, все преподнесено на простом языке на элементарных решениях. Легко повторить и понять. Дальше каждый может развивать в своем направлении. А профессиональные решения — это на радиокот.
+
avatar
0
Это смотря что вы подразумеваете под «ina219
Спасибо за быстрый ответ, имел ввиду готовый модуль, вечером попробую сеобрать, а то у меня как раз нагрузка навернулась.А тут такой вариант как раз для такого чайника как я.Да ещё попробую к ней экран прикрутить 16 2, шоб без компа.
+
avatar
  • ksiman
  • 07 января 2021, 16:57
+2
Спасибо товарищу Ksiman за идею.
На здоровье :)
Программная ООС по току всё-же имеет право на жизнь, несмотря на множество недостатков.
+
avatar
  • u3712
  • 07 января 2021, 21:33
0
Главное — не делать ее в boost.
(Делал, даже на stm32 выходит погано, но там это было вынужденно. Впрочем, и там пришлось таки поставить nсp1450, в ущерб пмехам.)
+
avatar
0
Каждый раз натыкаясь на подобные темы мой внутренний еврей настойчиво вопрошает:
Почему бы при разрядке не перегонять энергию в другие аккумуляторы?
Я понимаю что 0,01кВт*ч с одного аккума это мизер, но при массовых тестах мы имеем более солидные цифры и обильное тепловыделение
+
avatar
  • kirich
  • 07 января 2021, 22:20
+3
Почему бы при разрядке не перегонять энергию в другие аккумуляторы?
Есть устройства которые умеют делать и это, причем в таком режиме они имеют больше мощность.
Мало того, есть и обычные электронные нагрузки, в смысле не только для тестов аккумуляторов, которые имеют функцию рекуперации в сеть.

Но в первом случае можно случайно упереться в то, что аккумулятор который работает как нагрузка, уже зарядился, а тест надо продолжать, да и зачем нужен дополнительный износ аккумуляторов.
Во втором случае цена, такие устройства дороже.
Кроме того это усложняет конструкцию.

при массовых тестах мы имеем более солидные цифры
А куда потом девать заряженные аккумуяторы при массовых тестах, разряжать другой нагрузкой? :)
+
avatar
  • u3712
  • 07 января 2021, 22:43
0
У меня был в разработке подобный блок нагрузок — перегонял мощность на высокое для питания тестового БП. При общей мощности в кВт рассеивание тепла не столь громоздкое.
Но это фигня, бредит проект для моторчика под параплан со смешной мощностью и не менее смешной нагрузкой на батарейки. Плюс «нагрузка» (механическая) на этот моторчик. Поэтому прорабатывается вариант с удвоенным количеством моторов (наша разработка — мотор) по концепту мотор-генератор. Но и это мелочи. Моторчик только демашка для натурных испытаний. Одна пятая нормальной версии. Впрочем, вряд-ли будет сделано.
Так что, возврат энергии на высокую часть — вполне нормальное решение. Да и не особо сложное.
+
avatar
  • kirich
  • 07 января 2021, 23:29
0
Так что, возврат энергии на высокую часть — вполне нормальное решение.
Так я и не писал, что это плохое решение.

Да и не особо сложное
Все относительно…
+
avatar
  • INN36
  • 08 января 2021, 07:46
0
Видим погрешность измерения заряда («ёмкости») и энергии 2% и 4%, соответственно.
Мне кажется, что термин «погрешность» применен не к месту. Потому как в «эталонных» источниках 1 и 2 не приведены результаты измерений того же самого экземпляра. И вообще, во всех трех случаях замеры проведены для трех разных экземпляров ХИЭЭ (ХИТ). Как их можно сравнивать? )
+
avatar
  • BSP
  • 08 января 2021, 21:28
0
Отклонение? Различие?
Сравнивать, действительно, не совсем верно, но нужно было хоть примерно прикинуть.
+
avatar
  • Aahz
  • 08 января 2021, 14:09
+5
Интересная самоделка. Чуть причесал схему
+
avatar
  • BSP
  • 08 января 2021, 21:19
+1
Ваша вариация красива, но я не зря рисовал провода «как есть», и не пользовался сокращением в виде зака «общий».
Теперь представьте, что у каждого провода есть сопротивление.
Вы прицепили делитель напряжения таким образом, что включили сопротивление провода от плюсового вывода аккумулятора до прехохрвнителя., похоронив тем самым преимещуства трёхпроводной схемы измерения. Также вы вносите некую неопределённость, куда подключать землю ардуино. На моей схеме явно указано что куда, а по Вашей версии схемы есть шанс включить ардуино таким образом, что часть сопротивление провода от шунта до ардуино будет являться шуном.
+
avatar
  • u3712
  • 08 января 2021, 21:37
+3
По правилам выполнения, электрическая схема не должна отображать конструктивное исполнение устройства. Особенность исполнения «земли», как и другие нюансы, идут в сопроводительной документации от разработчика к конструктору и не отображаются на электрической схеме. ))
+
avatar
  • BSP
  • 08 января 2021, 21:45
+4
Ой всё))
+
avatar
  • rx3apf
  • 10 января 2021, 23:24
+2
Особенность исполнения «земли», как и другие нюансы, идут в сопроводительной документации от разработчика к конструктору и не отображаются на электрической схеме. ))
А вот тут Вы ошибаетесь. Критичные к топологии узлы очень даже часто (и вообще это хороший тон) отображаются на принципиальной схеме, когда куча «земляных» проводников сползается к одной явно указанной точке. Да и «другие нюансы» — типа умощненных шин, или узлов, находящихся под высоким напряжением или критичных к типу компонентов на принципиальных схемах тоже нередко выделяются.
+
avatar
0
Помнится, так была начерчена схема КРЕН22А.
+
avatar
  • Aahz
  • 10 января 2021, 12:09
0
В Вашем исполнении схемы видны традиции великих мастеров прошлого. Традиции таких мастеров как Пикассо и Дали.