avatar_Паяка

Универсальный модуль разрядной моргалки для Ардуино и не только

Автор Паяка, 17 Сен. 2018 в 15:11

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

WERAS

Паяка вы бы лучше результаты о работе как все запустите,лучше видео,как ток держит, т.д.

Паяка

[user]WERAS[/user], соберу, допишу прошивку, буду испытывать. Кстати, прошивка использует 14-битный ШИМ, в Атмеге328 он есть, вплоть до 16 бит 250 Гц, (с 16 МГц кварцем), но мне захотелось 1 кГц). AnalogWrite() этого не умеет, потому не все ардуинщики знают.

Эту же прошивку, (исходник выложу), можно будет использовать и для линейной электронной нагрузки, когда ШИМ управляет не прерыванием тока резистора, а через интегратор и ОУ током транзисторов в линейном режиме. (ИТУН, источник тока, управляемый напряжением). Получается действительно универсальная прошивка, без необходимости применения внешнего ЦАП.
GT TF1 60V 20Ah Chilwee DZF

WERAS

Доброго дня Господа! Скажите пожалуйста,вот китайские эл. нагрузки
на что они нагружены? Нет ни одного нагрузочного резистора.Не бывает же так,что бы нагружено прямо на полевик  S-D,как бы кз.написано что 18 бит контроллер

serggio

[user]WERAS[/user], мосфет работает в режиме линейной нагрузки. Он и «преобразует ток в тепло» ;)
Контроллер 16 бит, АЦП 18 бит внешний, 2 шт

Паяка

#94
Получился такой скетч. Осталось собрать само устройство, перепроверить и испытать.

Скетч
#define voltage_ADC_pin 1
#define current_ADC_pin 2

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // обычная распиновка LCD Keypad Shield

// Выводы 5/PD5/OC0B, 6/PD6/OC0A используют тот же таймер, что и millis() с delay(),
// потому их применение для ШИМ нежелательно. Они ещё и заняты.
// Зато 10/PB2/OC1B поддерживает 16-разрядный ШИМ, чем мы и воспользуемся!
// 16 МГц / 65536 = 244 Гц при 16 бит, 1 кГц при 14 бит - приемлемая частота ШИМ.
#define PWM_pin 10
#define inv_duty_cycle OCR1B // инвертированный рабочий цикл.
// Неинвертированный был бы удобнее, но при Кзап = 0 у быстрого ШИМ Атмеги
// проскакивает игла, что для 20-амперной нагрузки недопустимо.
// От полярности драйвера ключа это не зависит: специфика Атмеги.

byte ctrl_flags = 0; // оперативные флаги
#define encoder_act   0 // энкодер повёрнут
#define encoder_inc   1 // по часовой стрелке
#define key_pressed   2 // нажата кнопка
#define must_refresh  3 // пора обновить показания

byte on_off = 0;          // генеральные флаги
#define dscg_on            0 // выключатель разряда
#define dscg_pause         1 // пауза по нижнему порогу
#define hold_screen        2 // остаёмся в текущем экране
#define voltage_calibrated 3 // напряжение откалибровано
#define current_calibrated 4 // ток откалиброван
#define measure_process    5 // идёт измерение

byte exit_code; // передача управления между режимами
#define exit_code_main    0
#define exit_code_setup   1
#define exit_code_calibr  2
#define exit_code_reset   3

unsigned int voltage; // напряжение АКБ: 12 бит, цена деления 5 мВ (без калибровки 4.88)
unsigned int voltage_meas; // временная для измерения напряжения АКБ
unsigned int voltage_raw;

unsigned int current; // ток: 14 бит, цена деления 100/41 (примерно 2,44) мА
unsigned int current_meas; // временная для измерения тока
unsigned int current_raw;
unsigned int current_set; // установка тока: 14 бит, цена деления 100/41 мА
unsigned int current_set_decimal; // отображение установки тока: 9 бит, цена деления 100 мА
unsigned int low_volt_set; // нижний порог качелей: 12 бит, цена деления 5 мВ
unsigned int hi_volt_set; // верхний порог качелей: 12 бит, цена деления 5 мВ

unsigned long millis_old; // время предыдущего измерения

unsigned int voltage_k; // наклон калибровки напряжения
unsigned int voltage_b; // смещение калибровки напряжения
unsigned int current_k; // наклон калибровки тока
unsigned int current_b; // смещение калибровки тока
unsigned long uah;  // микроампер-часы, цена деления 1 мкА*ч
unsigned long uwh; // микроватт-часы, цена деления 10 мкВт*ч
unsigned long from_millis = 0; // учёт сброса
unsigned int voltage_point1x = 0; // точка калибровки напряжения - 1
unsigned int voltage_point1y = 0; // точка калибровки напряжения - 1
unsigned int voltage_point2x = 0; // точка калибровки напряжения - 2
unsigned int voltage_point2y = 0; // точка калибровки напряжения - 2
unsigned int current_point1x = 0; // точка калибровки тока - 1
unsigned int current_point1y = 0; // точка калибровки тока - 1
unsigned int current_point2x = 0; // точка калибровки тока - 2
unsigned int current_point2y = 0; // точка калибровки тока - 2

#include <EEPROM.h>

void load_settings() { // Загрузка настроек и констант из EEPROM
EEPROM.get(0,  current_set_decimal);
EEPROM.get(2,  low_volt_set);
EEPROM.get(4,  hi_volt_set);
EEPROM.get(6,  voltage_k);
EEPROM.get(8,  voltage_b);
EEPROM.get(10, current_k);
EEPROM.get(12, current_b);
EEPROM.get(14, uah);
EEPROM.get(18, uwh);
EEPROM.get(22, voltage_point1x);
EEPROM.get(24, voltage_point1y);
EEPROM.get(26, current_point1x);
EEPROM.get(28, current_point1y);

// выставляем флаги, чтобы не делать проверки лишний раз
if (voltage_k > 0) bitSet (on_off,voltage_calibrated);
if (current_k > 0) bitSet (on_off,current_calibrated);

current_set = current_set_decimal * 82;
}

void save_results() { // сохранение А*ч и Вт*ч
EEPROM.put(14, uah);
EEPROM.put(28, uwh); 
}

String string_tmp;
byte cursor_step;
byte cursor_max; // максимальная позиция курсора для экрана
byte cursor_col;
byte cursor_row;

void cursor_inc(){ // перемещение курсора вперёд
cursor_step++;
if (cursor_step > cursor_max) cursor_step = 0;
}

void cursor_dec(){ // перемещение курсора назад
if (cursor_step > 0) cursor_step--;
else cursor_step = cursor_max;
}

void dscg_start() { // включение разрядной нагрузки
bitSet (on_off,dscg_on);
// определим начальный Кзап для текущего напряжения: Rн = 0.5 Ом
// I = current_set * 2,44 мА
// U = voltage * 5 мВ
// I = Кзап * U/Rн
// Кзап = 0x3FFF (1 - inv_duty_cycle);
// current_set * 2,44 = Кзап * voltage * 5 / 0.5
// current_set * 2,44 = Кзап * voltage * 10
// current_set / 4.1 = Кзап * voltage -- округлим до 4, степень двойки
// потом ток оперативно подстроится
// Кзап = current_set / (voltage * 4)
// inv_duty_cycle = 0x3FFF (1 - current_set / (voltage * 4));
//
if (voltage > low_volt_set) {
  bitClear (on_off,dscg_pause);
  unsigned long curset_tmp = current_set * 0x1000; // 0x4000 / 4 = 0x1000
  inv_duty_cycle = 0x3FFF - (curset_tmp / voltage);
}
else dscg_wait();
}

void dscg_stop() { // выключение разрядного тока
inv_duty_cycle = 0x3FFF;
bitClear (on_off,dscg_on);
bitClear (on_off,dscg_pause);
save_results(); // сохраняем результаты при любом выключении
}

void dscg_wait() { // переход в паузу качелей
dscg_stop();
bitSet (on_off,dscg_on);
bitSet (on_off,dscg_pause);
}

void dscg_toggle(){ // включение разрядного тока
if bitRead (on_off,dscg_on) dscg_stop();
else dscg_start();
}

void indicate_results () { // отображение результатов
unsigned long long_tmp;
unsigned int tmp;
//1 22h 10.2Ah 133Wh
long_tmp = millis_old - from_millis;
tmp = long_tmp / 60000; // миллисекунды в минуты
if (tmp < 60) string_tmp = tmp + "m ";
else string_tmp = (tmp/60) + "h ";
// uah цена деления 1 мкА*ч
tmp = uah / 100000;
string_tmp += (tmp/10) + '.' + (tmp%10) + "Ah ";
// uwh цена деления 10 мкВт*ч
tmp = uwh / 100000;
if (tmp < 1000) string_tmp = tmp + "Wh";
else {
  tmp /= 100;
  string_tmp = (tmp/10) + '.' + (tmp%10) + "kW";
}
lcd.setCursor(0,1);
lcd.print(string_tmp);
lcd.setCursor(cursor_col, cursor_row);
lcd.cursor(); 
}

byte main_screen() {
// Главный экран:
//  0123456789ABCDEF
//0 ON  12.47V 18.5A
//1 22h 10.2Ah 133Wh
cursor_step = 0;
cursor_max = 2;
unsigned int tmp;
bitSet (on_off,hold_screen);
while bitRead (on_off,hold_screen) {
  while (ctrl_flags == 0) {}  // ждём программного прерывания
  if bitRead (ctrl_flags,encoder_act) {
   bitClear (ctrl_flags,encoder_act);
   if bitRead (ctrl_flags,encoder_inc) {
    bitClear (ctrl_flags,encoder_inc);
    cursor_inc();
   }
   else cursor_dec();
   switch (cursor_step) {
    case 0:
     cursor_col = 1;
     cursor_row = 0;
    break;
    case 1:
     cursor_col = 0xA;
     cursor_row = 0;
    break;
    case 2:
     cursor_col = 0xA;
     cursor_row = 1;
    break;
   }
   lcd.setCursor(cursor_col, cursor_row);
   lcd.cursor();
   delay (50);
  }
  if bitRead (ctrl_flags,key_pressed) {
   bitClear (ctrl_flags,key_pressed);
   switch (cursor_step) {
    case 1:
     bitClear (on_off,hold_screen);
     return exit_code_setup;
    break;
    case 2:
     bitClear (on_off,hold_screen);
     return exit_code_reset;
    break;
    default:
     dscg_toggle();
     bitSet (ctrl_flags,must_refresh);
    break;
   }
   delay (50);
  }
  if bitRead (ctrl_flags,must_refresh) {
   bitClear (ctrl_flags,must_refresh);
   //0 ON  12.47V 18.5A
   if bitRead (on_off,dscg_on) {
    if bitRead (on_off,dscg_pause) string_tmp="PAU ";
    else string_tmp="ON  ";
   }
   else string_tmp="OFF ";
   tmp = voltage / 2; // получаем цену деления 10 мВ
   string_tmp += (tmp/100) + '.' + (tmp%100) + "V ";
   tmp = current / 41; // получаем цену деления 100 мА
   string_tmp += (tmp/10) + '.' + (tmp%10) + 'A';
   lcd.setCursor(0,0); 
   lcd.print(string_tmp);
   indicate_results ();
  } 
}
}


void decimal_inc (byte parameter, byte limit) { // инкремент разряда параметра
parameter++;
if (parameter > limit) parameter = 0;
}

void decimal_dec (byte parameter, byte limit) { // декремент разряда параметра
if (parameter == 0) parameter = limit; 
else parameter--;
}

void parameter_edit (byte parameter, byte limit) { // редактирование цифры
while (!bitRead (ctrl_flags,key_pressed)) {
  if bitRead (ctrl_flags,encoder_act) {
   if bitRead (ctrl_flags,encoder_inc) {
    decimal_inc(parameter,limit);       
    bitClear (ctrl_flags,encoder_inc);
   }
   else decimal_dec(parameter,limit);
   delay (50);
   lcd.print(String(parameter));
   lcd.setCursor(cursor_col, cursor_row);
  }
  bitClear (ctrl_flags,encoder_act);
}
bitClear (ctrl_flags,key_pressed); 
}

byte lowvolt_0; // десятичные
byte lowvolt_1; // разряды
byte lowvolt_2; // редактируемых
byte hivolt_0;  // параметров
byte hivolt_1;
byte hivolt_2;
byte curset_0;
byte curset_1;
byte curset_2;

void set_parameters () {
low_volt_set = 2000 + lowvolt_2 * 200 + lowvolt_1 * 20 + lowvolt_0 * 2;
hi_volt_set = 2000 + hivolt_2 * 200 + hivolt_1 * 20 + hivolt_0 * 2; 
current_set_decimal = curset_2 * 100 + curset_1 * 20 + curset_0;
current_set = current_set_decimal * 82;
if (current_set > 0x3FFF) current_set = 0x3FFF;
// сохраняем в EERPOM
EEPROM.put(0, current_set_decimal);
EEPROM.put(2, low_volt_set);
EEPROM.put(4, hi_volt_set);
}

byte setup_screen() {
// Экран настройки порогов и тока:
//  0123456789ABCDEF
//0 11.05-12.00 20.0
//1 OK CANCEL CALIBR
dscg_stop();
cursor_step = 0;
cursor_max = 11;
unsigned int v_tmp = (low_volt_set / 2) - 1000;
lowvolt_0 = v_tmp % 10;
lowvolt_1 = (v_tmp % 100) / 10;
lowvolt_2 = v_tmp / 100;
v_tmp = (hi_volt_set / 2) - 1000;
hivolt_0 = v_tmp % 10;
hivolt_1 = (v_tmp % 100) / 10;
hivolt_2 = v_tmp / 100;
curset_0 = current_set_decimal % 10;
curset_1 = (current_set_decimal % 100) / 10;
curset_2 = current_set_decimal / 100;

bitSet (on_off,hold_screen);
while bitRead (on_off,hold_screen) {
  while (ctrl_flags == 0) {}  // ждём программного прерывания
  if bitRead (ctrl_flags,encoder_act) {
   if bitRead (ctrl_flags,encoder_inc) {
    cursor_inc();
    bitClear (ctrl_flags,encoder_inc);
   }
   else cursor_dec();
   switch (cursor_step) {
    case 0:
     cursor_col = 1;
     cursor_row = 0;
    break;
    case 1:
     cursor_col = 3;
     cursor_row = 0;
    break;
    case 2:
     cursor_col = 4;
     cursor_row = 0;
    break;
    case 3:
     cursor_col = 7;
     cursor_row = 0;
    break;
    case 4:
     cursor_col = 9;
     cursor_row = 0;
    break;
    case 5:
     cursor_col = 10;
     cursor_row = 0;
    break;
    case 6:
     cursor_col = 0xC;
     cursor_row = 0;
    break;
    case 7:
     cursor_col = 0xD;
     cursor_row = 0;
    break;
    case 8:
     cursor_col = 0xF;
     cursor_row = 0;
    break;
    case 9:
     cursor_col = 0;
     cursor_row = 1;
    break;
    case 10:
     cursor_col = 3;
     cursor_row = 1;
    break;
    case 11:
     cursor_col = 10;
     cursor_row = 1;
    break;
   }
   lcd.setCursor(cursor_col, cursor_row);
   lcd.cursor();
   delay (50);
   bitClear (ctrl_flags,encoder_act);
  }
  if bitRead (ctrl_flags,key_pressed) {
   bitClear (ctrl_flags,key_pressed);
   switch (cursor_step) {
    case 0:
     parameter_edit(lowvolt_2,4);
    break;
    case 1:
     parameter_edit(lowvolt_1,9);
    break;
    case 2:
     parameter_edit(lowvolt_0,9);
    break;
    case 3:
     parameter_edit(hivolt_2,4);
    break;
    case 4:
     parameter_edit(hivolt_1,9);
    break;
    case 5:
     parameter_edit(hivolt_0,9);
    break;
    case 6:
     parameter_edit(curset_2,1);
    break;
    case 7:
     parameter_edit(curset_1,9);
    break;
    case 8:
     parameter_edit(curset_0,9);
    break;
    case 9: // OK
     set_parameters ();
     return exit_code_main;
     bitClear (on_off,hold_screen);
    break;
    case 10: // CANCEL
     return exit_code_main;
     bitClear (on_off,hold_screen);
    break;
    case 11: // CALIBRATE
     set_parameters ();
     return exit_code_calibr;
     bitClear (on_off,hold_screen);
    break;
   }
   delay (50);
  }
  if bitRead (ctrl_flags,must_refresh) {
   bitClear (ctrl_flags,must_refresh);
   //0 11.05-12.00 20.0
   string_tmp = '1' + lowvolt_2 + '.' + lowvolt_1 + lowvolt_0 + '-';
   string_tmp += '1' + hivolt_2 + '.' + hivolt_1 + hivolt_0 + ' ';
   string_tmp += curset_2 + curset_1 + "." + curset_0;
   lcd.setCursor(0,0); 
   lcd.print(string_tmp);
   //1 OK CANCEL RE-CAL
   //1 OK CANCEL CALIBR
   string_tmp = "OK CANCEL ";
   if (bitRead (on_off,current_calibrated) & bitRead (on_off,current_calibrated)) string_tmp += "RE-CAL";
   else string_tmp += "CALIBR";
   lcd.setCursor(0,1);
   lcd.print(string_tmp);
   lcd.setCursor(cursor_col, cursor_row);
   lcd.cursor(); 
  } 
}
}

byte now_calibrating = 0; // какую точку калибруем в данный момент
#define point2_now  0 // вторая точка, если бит true
#define current_now 1 // ток, если бит true
#define voltage_point1_now 0
#define voltage_point2_now 1
#define current_point1_now 2
#define current_point2_now 3

byte point_0; // десятичные разряды
byte point_1; // точек калибровки
byte point_2;
byte point_3;

void load_point() { // загрузка точки калибровки
unsigned int tmp;
if bitRead (now_calibrating,current_now) {
  // калибруем ток
  if (current_point1x > 0) bitSet (now_calibrating,point2_now);
  else bitClear (now_calibrating,point2_now);
  // current: 14 бит, цена деления 100/41 (примерно 2,44) мА
  // надо получить цену деления 10 мА
  tmp = current * 10 / 41;
}
else { // калибруем напряжение
  if (voltage_point1x > 0) bitSet (now_calibrating,point2_now);
  else bitClear (now_calibrating,point2_now);
  // voltage: 12 бит, цена деления 5 мВ
  tmp = voltage / 2;
}
point_0 = tmp % 10;
point_1 = (tmp % 100) / 10;
point_2 = (tmp % 1000) / 100;
point_3 = tmp / 1000;
bitSet (ctrl_flags,must_refresh);
}

void save_point() { // сохранение и обработка точки калибровки
unsigned int tmp = point_0  + (point_1 * 10) + (point_2 * 100) + (point_3 * 1000);
long longtmp1;
long longtmp2;
int signed_tmp;
int difference;
switch (now_calibrating) {
  case voltage_point1_now:
   voltage_point1x = voltage_raw;
   voltage_point1y = tmp * 2;
   EEPROM.put(22, voltage_point1x);
   EEPROM.put(24, voltage_point1y);
  break;
  case current_point1_now:
   current_point1x = current_raw;
   current_point1y = tmp * 41 / 10;
   EEPROM.put(26, current_point1x);
   EEPROM.put(28, current_point1y);
  break;
  case voltage_point2_now:
   voltage_point2x = voltage_raw;
   voltage_point2y = tmp * 2;
   // калибровать будем по 2 точкам
   // y = kx+b
   // k = (y2-y1)/(x2-x1)
   // b = (x2y1-x1y2)/(x2-x1)
   // calibr_tmp = voltage_raw * voltage_k;
   // tmp = calibr_tmp / 4096;
   // voltage = tmp + voltage_b - 2048;
   longtmp1 = (voltage_point2y - voltage_point1y) * 4096;
   difference = voltage_point2x - voltage_point1x;
   signed_tmp = longtmp1 / difference;
   voltage_k = abs(signed_tmp);
   longtmp1 = voltage_point2x * voltage_point1y;
   longtmp2 = voltage_point1x * voltage_point2y;
   longtmp1 -= longtmp2;
   signed_tmp = longtmp1 / difference;
   signed_tmp += 2048;
   voltage_b = abs(signed_tmp);
   EEPROM.put(6,  voltage_k);
   EEPROM.put(8,  voltage_b);
   bitSet (on_off,voltage_calibrated);   
  break;
  case current_point2_now:
   current_point2x = current_raw;
   current_point2y = tmp * 41 / 10;
   // calibr_tmp = current_raw * current_k;
   // tmp = calibr_tmp / 16384;
   // current = tmp + current_b - 8192;
   longtmp1 = (current_point2y - current_point1y) * 16384;
   difference = current_point2x - current_point1x;
   signed_tmp = longtmp1 / difference;
   voltage_k = abs(signed_tmp);
   longtmp1 = current_point2x * current_point1y;
   longtmp2 = current_point1x * current_point2y;
   longtmp1 -= longtmp2;
   signed_tmp = longtmp1 / difference;
   signed_tmp += 8192;
   voltage_b = abs(signed_tmp);
   EEPROM.put(10, current_k);
   EEPROM.put(12, current_b);
   bitSet (on_off,current_calibrated);   
  break;
}
load_point();
}

void toggle_point() { // переключение режимов калибровки
if bitRead (now_calibrating,current_now) { // переключаем в режим калибровки напряжения
  bitClear (now_calibrating,current_now);
  dscg_stop();
  load_point();
}
else { // переключаем в режим калибровки тока
  bitSet (now_calibrating,current_now);
  load_point();
  dscg_start();
}
}

byte calibr_screen () {
// Экран калибровки:
//  0123456789ABCDEF
//0 CURRENT-1 24.70A
//1 CANCEL    CALIBR
//0 VOLTAGE-2 12.47V
dscg_stop();
bitClear (now_calibrating,current_now);
cursor_step = 0;
cursor_max = 6;
load_point();
bitSet (on_off,hold_screen);
while bitRead (on_off,hold_screen) {
  while (ctrl_flags == 0) {}  // ждём программного прерывания
  if bitRead (ctrl_flags,encoder_act) {
   if bitRead (ctrl_flags,encoder_inc) {
    cursor_inc();
    bitClear (ctrl_flags,encoder_inc);
   }
   else cursor_dec();
   switch (cursor_step) {
    case 0:
     cursor_col = 0;
     cursor_row = 0;
    break;
    case 1:
     cursor_col = 0xA;
     cursor_row = 0;
    break;
    case 2:
     cursor_col = 0xB;
     cursor_row = 0;
    break;
    case 3:
     cursor_col = 0xD;
     cursor_row = 0;
    break;
    case 4:
     cursor_col = 0xE;
     cursor_row = 0;
    break;
    case 5:
     cursor_col = 0;
     cursor_row = 1;
    break;
    case 6:
     cursor_col = 10;
     cursor_row = 1;
    break;
   }
   lcd.setCursor(cursor_col, cursor_row);
   lcd.cursor();
   delay (50);
   bitClear (ctrl_flags,encoder_act);
  }
  if bitRead (ctrl_flags,key_pressed) {
   bitClear (ctrl_flags,key_pressed);
   switch (cursor_step) {
    case 0:
     toggle_point();
    break;
    case 1:
     if bitRead (now_calibrating,current_now) parameter_edit(point_3,2);
     else parameter_edit(point_3,1);
    break;
    case 2:
     parameter_edit(point_2,9);
    break;
    case 3:
     parameter_edit(point_1,9);
    break;
    case 4:
     parameter_edit(point_0,9);
    break;
    case 5: // CANCEL
     dscg_stop();
     return exit_code_setup;
     bitClear (on_off,hold_screen);
    break;
    case 6: // CALIBRATE
     save_point();
    break;
   }
   delay (50);
  }
  if bitRead (ctrl_flags,must_refresh) {
   bitClear (ctrl_flags,must_refresh);
   //0 CURRENT-1 24.70A
   //1 CANCEL    CALIBR
   //0 VOLTAGE-2 12.47V
   if bitRead (now_calibrating,current_now) string_tmp = "CURRENT-";
   else string_tmp = "VOLTAGE-";
   if bitRead (now_calibrating,point2_now) string_tmp += "2 ";
   else string_tmp += "1 ";
   if bitRead (now_calibrating,current_now)
    string_tmp += point_3 + point_2 + '.' + point_1 + point_0 + 'A';
   else string_tmp += point_3 + point_2 + point_1 + '.' + point_0 + 'V';
   lcd.setCursor(0,0); 
   lcd.print(string_tmp);
   lcd.setCursor(0,1);
   lcd.print("CANCEL    CALIBR");
   lcd.setCursor(cursor_col, cursor_row);
   lcd.cursor(); 
  } 
}
}

byte reset_screen () {
// Экран сброса показаний:
//  0123456789ABCDEF
//0 RESET?   NO  YES
//1 22h 10.2Ah 133Wh
cursor_step = 0;
cursor_max = 1;
bitSet (on_off,hold_screen);
while bitRead (on_off,hold_screen) {
  while (ctrl_flags == 0) {}  // ждём программного прерывания
  if bitRead (ctrl_flags,encoder_act) {
   bitClear (ctrl_flags,encoder_act);
   if bitRead (ctrl_flags,encoder_inc) {
    bitClear (ctrl_flags,encoder_inc);
    cursor_inc();
   }
   else cursor_dec();
   switch (cursor_step) {
    case 0:
     cursor_col = 9;
     cursor_row = 0;
    break;
    case 1:
     cursor_col = 0xD;
     cursor_row = 0;
    break;
   }
   lcd.setCursor(cursor_col, cursor_row);
   lcd.cursor();
   delay (50);
  }
  if bitRead (ctrl_flags,key_pressed) {
   bitClear (ctrl_flags,key_pressed);
   switch (cursor_step) {
    case 0:
     bitClear (on_off,hold_screen);
     return exit_code_main;
    break;
    case 1:
     uah = 0;
     uwh = 0;
     EEPROM.put(14, uah);
     EEPROM.put(18, uwh);
     from_millis = millis();
     bitClear (on_off,hold_screen);
     return exit_code_main;
    break;
   }
   delay (50);
  }
  if bitRead (ctrl_flags,must_refresh) {
   bitClear (ctrl_flags,must_refresh);
   //0 RESET?   NO  YES
   string_tmp="RESET?   NO  YES";
   lcd.setCursor(0,0); 
   lcd.print(string_tmp);
   //1 22h 10.2Ah 133Wh
   indicate_results ();
  } 
}
}


#define encoder_clk 2 // 2/PD2/INT0
#define encoder_int 0
#define encoder_dat 11
void encoder_ctrl() { // обработчик поворота энкодера
if (!bitRead(ctrl_flags,encoder_act)) { // если поворот не обработан, не делаем ничего
  if (digitalRead(encoder_dat)) bitSet(ctrl_flags,encoder_inc);
  bitSet(ctrl_flags,encoder_act);
}
}

#define button 3 // 3/PD3/INT1
#define button_int 1
void button_ctrl() { // обработчик нажатия кнопки
bitSet(ctrl_flags,key_pressed);
}

byte measure_counter; // счётчик измерений

void measure() { // измерение напряжения и тока
//analogRead() занимает 100 мкс, это приемлемо
// 128 измерений до усреднения дают 78 Гц
bitSet (on_off,measure_process);
voltage_meas = 0;
current_meas = 0;
for (byte i=0; i<64; i++) { // 10бит*64 = 16 бит
  voltage_meas += analogRead(voltage_ADC_pin);
  current_meas += analogRead(current_ADC_pin);
}

// analogRead(voltage_ADC_pin) = 1024 (U-2Vdd)/Vdd
// voltage = analogRead(voltage_ADC_pin) + 2048
// 12 бит, цена деления 5 мА
// т.е. достаточно включить 12-й разряд (11 с 0)
voltage = (voltage_meas / 64) + 2048;
voltage_raw = voltage;
unsigned long calibr_tmp;
unsigned int tmp;
if (bitRead (on_off,voltage_calibrated)) {
  calibr_tmp = voltage_raw * voltage_k;
  tmp = calibr_tmp / 4096;
  voltage = tmp + voltage_b - 2048;
}

// Виртуальный шунт 0.25 Ом 1В/4А
// analogRead(current_ADC_pin) = 1024*0.25I/Vdd = 51.2I
// current: 14 бит, цена деления 100/41 мА
current = current_meas / 4;
current_raw = current;
if (bitRead (on_off,current_calibrated)) {
  calibr_tmp = current_raw * current_k;
  tmp = calibr_tmp / 16384;
  current = tmp + current_b - 8192;
  // Учтём возможный отрицательный результат из-за дрейфа параметров
  if (current > 16384) current = 0;
}

// учитываем потребление тока в любом случае
measure_counter++;
if (measure_counter >= 32) { // примерно раз в полсекунды-секунду
  measure_counter = 0;
  unsigned int millis_passed = millis() - millis_old;
  millis_old = millis();
  // uah  цена деления 1 мкА*ч
  // (100/41)мА * (1/3600)(ч/1000) = 1/3476 мкА*ч
  calibr_tmp = current * millis_passed / 3476; // цена деления 1 мкА*ч
  uah += calibr_tmp;
  calibr_tmp *= voltage; // цена деления 5 нВт*ч
  uwh += calibr_tmp / 2000;  // цена деления 10 мкВт*ч
  bitSet (ctrl_flags,must_refresh);
}

if (bitRead (on_off,dscg_on)) { // если режим разряда активен
  if (bitRead (on_off,dscg_pause)) { // если ждём верхнего порога
   if (voltage >= hi_volt_set) dscg_start(); 
   // достигли верхнего порога, включаем
  }
  else { // если разряд идёт
   if (voltage <= low_volt_set) dscg_wait(); 
   // достигли нижнего порога, отключаем
   else { // не достигли нижнего порога, подстраиваем ток
    if ((current > current_set) & (inv_duty_cycle<0x3FFF)) inv_duty_cycle++;
    else if ((current < current_set) & (inv_duty_cycle>0)) inv_duty_cycle--;
   }
  }
}
bitClear (on_off,measure_process);
}

ISR(TIMER2_OVF_vect){ // запуск измерения по прерыванию
  // переполнения TIMER2
if (!bitRead (on_off,measure_process)) measure();
}

void setup() {
// Процедура инициализации, выполняется однажды при запуске МК.
// Первым делом, настроим и запустим аппаратный ШИМ с Кзап=100%!
noInterrupts();
inv_duty_cycle = 0x3FFF;
TCCR1A = (1<<COM1B1)|(1<<WGM11);
// Clear OC1B on Compare Match, set OC1B at BOTTOM (non-inverting mode)
TCCR1B = (1<<WGM13)|(1<<WGM12)|(1<<CS10); //mode14 FastPwm
ICR1 = 0x3FFF; // 14 бит 1 кГц
pinMode(PWM_pin, OUTPUT);
interrupts();

// Загрузим настройки и калибровочные константы!
load_settings();

// Теперь можно инициализировать дисплей!
lcd.begin(16, 2); // 2 строки по 16 символов

measure_counter = 0;
millis_old = millis();
// Оставшийся свободным таймер TC2 настроим для тактирования измерений
// 16 МГц / 1024 / 256 = 61 Гц
TCCR2B = (1<<CS20)|(1<<CS21)|(1<<CS22); // предделитель 1024
TIMSK2 = (TIMSK2 & B11111110); // прерывание по переполнению

// Напоследок, настроим прерывания от энкодера и кнопки
attachInterrupt(encoder_int, encoder_ctrl, RISING);
pinMode(encoder_clk, INPUT);
pinMode(encoder_dat, INPUT);
attachInterrupt(button_int, button_ctrl, RISING);
pinMode(button, INPUT);

// Наконец, запустим главный экран
exit_code = main_screen();
}

void loop() { // вечный цикл
switch (exit_code) {
  case exit_code_setup:    exit_code = setup_screen();    break;
  case exit_code_calibr:   exit_code = calibr_screen();   break;
  case exit_code_reset:    exit_code = reset_screen();    break;
  default:                 exit_code = main_screen();     break;
}
}

Скетч использует 9872 байт (30%) памяти устройства. Всего доступно 32256 байт.
Глобальные переменные используют 253 байт (12%) динамической памяти, оставляя 1795 байт для локальных переменных. Максимум: 2048 байт.

Микропрограмма асинхронная, многозадачная, реализована методом конечных автоматов (машины состояний). Кроме аппаратных прерываний, используются программные флаги состояний, обрабатывающиеся, когда до их обработчиков доходит время. Разные промежутки времени между измерениями учитываются: при накоплении ватт-часов и ампер-часов умножение идёт именно на прошедшее с момента предыдущего измерения время, а не на сферическую константу в вакууме.

Код неоптимален с точки зрения производительности и занимаемых ПЗУ и ОЗУ, но роскошных ресурсов Атмеги более чем хватает. Когда-то компьютеры с такими параметрами были, теперь микроконтроллер.

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

Для калибровки и перекалибровки прибора достаточно иметь мультиметр и регулируемый источник питания, или пару АКБ с разными ЭДС. Калибровка вызывается из меню. Никаких калибровочных прошивок или переставляемых перемычек.
GT TF1 60V 20Ah Chilwee DZF

Dunkel

Цитата: Паяка от 19 Нояб. 2018 в 08:20Для калибровки и перекалибровки прибора достаточно иметь мультиметр и регулируемый источник питания, или пару АКБ с разными ЭДС.

А нужны ли все эти заморочки?
ina219/226 не требуют никакой калибровки, готовые модули для ардуино стоят около доллара.
Многофункциональная облачная моргалка/логгер:
https://morgalka78.wordpress.com/

elektrik897

Эволюция: Вымпел-55, Вымпел-57, Кулон-912, Вымпел-55, ТОР4, 2хТОР5, BL1204, TOP7, Бережок-V1.
Начало краш-теста.Таблица с результатами краш-теста.Архив журнала КТЦ с логами.

Яков93


Dunkel

Цитата: elektrik897 от 19 Нояб. 2018 в 14:35
Калибровать для точности все равно надо.
Спойлер

Эта калибровка никакого отношения к точности не имеет, читайте даташит.
Калибровочный регистр нужен для пересчета напряжения шунта в ток прямо в датчике, для ленивых.
Я, например, этим вообще не пользуюсь - просто получаю напряжение шунта, и сам перевожу его в ток.
Многофункциональная облачная моргалка/логгер:
https://morgalka78.wordpress.com/

Кобольд

#99
Подождите, до меня только сейчас дошло. Этото разрядник импульсный? То есть он моргает на разряд аккумулятора?

Если да, то скажите, а почему такая моргалка лучше, чем просто разряд заданным током (непрерывным)? Наверняка такое обсуждали, дайте ссылку, пожалуйста, почитать.

Паяка

[user]Кобольд[/user], он использует ШИМ для регулировки тока, но импульсы идут в нихромовый резистор, а перед проводами на АКБ сглаживаются батареей конденсаторов, чтобы было меньше радиопомех.

Суть моргалки в том, что есть режим разрядных качелей с выкачкой ёмкости. Можно выставить не только порог прекращения разряда, но и порог его возобновления. Т.е. ЭДС под нагрузкой упала до заданной величины, и прибор ждёт, пока НРЦ поднимется до верхнего порога, чтобы возобновить разряд. Если поднять этот верхний порог, например, до 13.3В или вообще вверх шкалы, он не будет достигнут никогда, и нагрузка отключится при достижении нижнего порога, т.е. будет разряд по ГОСТ.

А если разряжать АКБ импульсами в полезную нагрузку, теоретически можно достичь более высокого КПД и более полного использования ёмкости, (смягчить закон Пейкерта), но это ещё надо исследовать.

Сегодня-завтра доделаю разрядную моргалку, и приступлю к испытаниям. Надо ещё дополнить скетч защитой от деления на 0 при калибровке, если две точки совпадут, (что до меня только сегодня дошло).
GT TF1 60V 20Ah Chilwee DZF

serggio

#101
Цитата: Паяка от 22 Нояб. 2018 в 21:29он использует ШИМ для регулировки тока, но импульсы идут в нихромовый резистор, а перед проводами на АКБ сглаживаются батареей конденсаторов, чтобы было меньше радиопомех.
Импульсы не могут сглаживаться батареей конденсаторов, иначе ваша нагрузка будет работать на разряд этой батареи конденсаторов, а не АКБ. Батарею конденсаторов вы прикрутили, чтобы не крякнул ваш ардуино, когда вы будете надевать клеммы на АКБ. Ведь вы никого не слушаете и решили запитать нагрузку напрямую от тестируемого источника.
Поэтому стоит убрать вам из названия слово "универсальный".

Кстати, на выбранных вами фетах вы не сможете переделать ее в линейную, погорят они у вас. Нужно было брать планарные IRF3710 или IRFP250.

Паяка

Цитата: serggio от 22 Нояб. 2018 в 21:59ваша нагрузка будет работать на разряд этой батареи конденсаторов, а не АКБ
Она на неё и работает, а батарея заряжается от АКБ.

Добавлено 22 Ноя 2018 в 22:22

Цитата: serggio от 22 Нояб. 2018 в 21:59когда вы будете надевать клеммы на АКБ
постепенно откроется ключ защиты от переполюсовки, и батарея зарядится без значительного скачка тока.

Добавлено 22 Ноя 2018 в 22:23

Цитата: serggio от 22 Нояб. 2018 в 21:59Поэтому стоит убрать вам из названия слово "универсальный".
Это не окончательный, а промежуточный вариант. Универсальный ещё будет.

Добавлено 22 Нояб. 2018 в 22:24

Цитата: serggio от 22 Нояб. 2018 в 21:59не сможете переделать ее в линейную, погорят они у вас. Нужно было брать планарные IRF3710 или IRFP250.
Взяты те, что были в распоряжении. Линейный вариант буду делать позже.
GT TF1 60V 20Ah Chilwee DZF

serggio

#103
И на какой частоте у вас будет ШИМ?

Цитата: Паяка от 22 Нояб. 2018 в 22:20Она на неё и работает, а батарея заряжается от АКБ.
Еще лучше... Т.е. Эта гирлянда будет постоянно разряжаться фетом? Тогда вопрос про частоту более чем актуальный.

Паяка

[user]serggio[/user], 1 кГц. Гирлянда мала, посмотрим, что будет.
GT TF1 60V 20Ah Chilwee DZF

serggio

Посмотрел управление гейтом на фете IRFP250N  в  своей нагрузке.
При 12,7 В на АКБ и 100 мА тока на гейт подается 4,02В. Каждые 100 мА нагрузки накидывают 0,08В (80 мВ) до 300 мА. Далее уже по 40 мВ и потом снова делиться. Далеко не залазил. Ессно линейный режим. Нужно добраться до управляющей ноги проца чтобы глянуть частоту ШИМ, но нужно снимать дисплей.

serggio

Посмотрел сигнал управления с микроконтроллера. По сути, частота ШИМ во всем диапазоне 0-10А 1,6667 кГц не меняется, изменяется только скважность.
Заполнение при 10А тока (макс для нагрузки на фото)


Яков93

Цитата: serggio от 22 Нояб. 2018 в 23:47
Посмотрел управление гейтом на фете IRFP250N  в  своей нагрузке.
При 12,7 В на АКБ и 100 мА тока на гейт подается 4,02В. Каждые 100 мА нагрузки накидывают 0,08В (80 мВ) до 300 мА. Далее уже по 40 мВ и потом снова делиться. Далеко не залазил. Ессно линейный режим. Нужно добраться до управляющей ноги проца чтобы глянуть частоту ШИМ, но нужно снимать дисплей.
А в Вашей нагрузке есть режим поиска точки максимальной мощности (MPPT)?
А то мне понадобилось узнать характеристики своих солнечных батарей, а вручную эту точку искать не особо удобно.
Я на свою самодельную электронную нагрузку такой алгоритм сочинил, осталось дождаться солнышка и испробовать.