Товары из Китая

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


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

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

А ещё чесались руки покодить на Arduino, и вот что из этого вышло.

Всем привет.

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

На муське уже был DIY обзор вполне годной самодельной электронной нагрузки.

В комментариях обсуждали плюсы и минусы данной схемы, а также мысли о программном поддержании тока разряда самой Ардуино.

За основу взял схему товарища Ksiman

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

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

Как видите, в железе она реально простая.

На схеме видим аккумулятор в трёхпроводной схеме подключения, который будет разряжаться через датчик тока 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 В

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

Выбираем нужный режим, напряжение отсечки, ток и запускаем процесс.

Управление подробно
Нажимаем 3, вводим значение напряжение отсечки:

Я выбрал 3000 мВ. Новые показания отобразились на панели управления.

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

Устанавливаем ток разряда.

Нажимаем 2, вводим ток в миллиамперах, например 3500

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

Нажимаем 1, процесс запускается.

Перед запуском программа проверяет наличие аккумулятора – если напряжение ниже 500мВ, то процесс не запустится, высветится сообщение.

До нуля при, этом, разрядить аккумулятор можно, выставив соответствующий Cut-off через меню.

В любой момент в процессе разрядки, как и в любом пункте меню, можно прерваться, отправив 0

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

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

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

При показаниях 3.910 мВ реальное напряжение на клеммах батареи 3.93В, а относительно нуля ардуины – всё те же 3.91В.

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

По достижении заданного напряжения или по нажатию 0 разряд прекращается.

остальные пункты меню
Пункт 4 меню для дебага – просто показывает показания АЦП. Нажать 0 для выхода в главное меню.

Пункт 5 слегка приоткрывает транзистор, не используя PD-регулятор, чтобы можно было померить ток амперметром. Был нужен для дебага, пригодится для калибровки. Нажать 0 для выхода в главное меню.

Пункт 6 позволяет переключать стиль отображения информации CSV или «нормальный»

CSV режим – удобен для отправки данных в эксель

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

Power – это на самом деле не мощность, а текущее значение ШИМ (1023 максимум, т.е. 5 вольт)

Calcs per interval говорит нам о том, сколько раз PD регулятор поработал за время между отправкой данных в com-порт (2 секунды по умолчанию).

Пока писал обзор, дописал и протестировал пункт 7 — переключение между режимами разряда постоянным током, сопротивлением или мощностью.

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

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

Относительно свеженькая VTC5, куплена около года назад, использовалась раз 30.

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

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

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

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

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

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

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

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

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

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

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

Как видим, на токах до 200 мА ловить нечего – практически постоянная ошибка в 25-30 мА.

Ток при этом стабильный, но неправильный. Можно было откалибровать программно, но это не спортивно, у нас всё-таки простая электронная нагрузка.

Начиная от 2,5 Ампер ошибка вызвана разогревом шунта, при обдуве его вентилятором схема выходит на заданный ток.

Последняя строка, выделенная синим — максимальный ток, который удалось получить с 5 вольт управляющего напряжения. Здесь использовал Arduino UNO — у неё нет диода по входу USB, и транзистор открывается лучше.

Погрешность по току стала меньше, т.к. здесь использовал маленький чит в виде обдува шунта. Будем считать, что я сделал шунт помощнее 🙂

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

Что касается измерения энергии:

Протестированная мной VTC5 по данным прибора имеет заряд в 2449 мАч и энергию в 8618 мВтч.

Из случайных источников взял энергию данного элемента и сравнил с тем, что у меня получилось.Простая электронная нагрузка на Arduino.

Источник 1

Источник 2

Видим погрешность измерения заряда («ёмкости») 2% и 4%, соответственно.

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

Если пересчитать полученную мной энергию с учётом погрешности в 33мВ (т.е. «увеличить» напряжение в расчёте на эти 33 мВ), то заряд получается 8703 мВтч, что на 1 % больше посчитанного ардуиной значения.

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

На затворе полевика всё ровно и гладко, давайте покажу сигнал на токовом шунте.

В открытом режиме осциллографа пульсации тока не видны, показываю в закрытом режиме (это когда постоянная составляющая сигнала отфильтровывается) на пределе 5 мв/дел, т.е. 1 клетка это 50 мА тока в нагрузке.

Осциллограмма меня не порадовала, была видна какая-то широченная помеха шириной аж в 2 клетки.

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

В одной клетке 200нс умещаются аж 3 периода, нетрудно посчитать период сигнала в 66, (6) нс и частоту в 15 мгц, что подозрительно похоже на частоту кварца Ардуино.

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

Однако в процессе разряда аккумуляторов ток всё же немного «дрожит», примерно на 1/10 ячейки, или 5 мА.

Выглядит вот так.

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

Мультиметр на «точном» пределе в 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 мА из-за нагрева резисторов, т.е. сборочку резисторов надо бы делать помощнее.

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

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

Мой вариант выполнен в классическом раздолбайско-макетном стиле

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

Настройка скетча
В начале скетча прописаны и прокомментированы все параметры, в том числе какие пины ардуино задействованы. Все настройки собраны в первых 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.

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

Надеюсь, было интересно. Спасибо за внимание.


СМОТРИ ТАКЖЕ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *