Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Manufacturing Technology >> Processo de manufatura

Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1

Componentes e Materiais

ComponenteQuantidadeObservações
Arduino UNO1
SparkFun I2C DAC Breakout - MCP47251
Regulador Linear Ajustável (LM317)1
Amplificador Operacional Dual LM358 (TI)1
Resistor 6.8k ohm2
Resistor 3.3k ohm1
Resistor 5.1k ohm1
Resistor 1k ohm1
Resistor 10 ohm (sensor corrente)1
Relé (genérico, 5V)1
Resistor 20k ohm1
Fonte Protoboard 5V/3.3V (SparkFun)1
Fonte 17V1
Módulo LCD I2C 1602 YwRobot1

Sobre o Projeto

Procurei carregador inteligente que carregue bateria 9V NiMH em poucas horas e não encontrei. Todos eram "burros": corrente de carga desconhecida, sem término automático ao completar carga. Isso sobrecarrega e reduz vida útil. Decidi criar carregador "inteligente".

Versão 1 mantém simplicidade: carga corrente constante, término automático (dV/dt), carga de manutenção (trickle), medição de carga transferida.

Próxima versão: descarga, medição de capacidade, ciclagem.

AVISO: Carregar bateria com corrente alta pode causar explosão ou incêndio. Não deixe carregador sem supervisão. Não tente carregar baterias não recarregáveis (alcalinas). Testado apenas com NiMH (uso por sua conta e risco; sem responsabilidade por danos por bugs no design/código). Outros tipos de bateria exigem modificação no código.

Teoria

Fatos úteis para entender parâmetros do carregador:

Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1

Parâmetros Básicos do Circuito de Carga

Bateria 9V geralmente tem 7 células em série (às vezes 6 ou 8). Regulador deve prover tensão de carga até 8×1.6=12.8V. Dropout do LM317 ~2V → fonte ~15V (sem contar queda no resistor sensor).

Para corrente máxima 200mA e resistor sensor 10Ω, queda adicional = 2V → fonte 17V necessária.

Célula totalmente descarregada pode ter tensão muito baixa, até negativa. Tensão mínima do regulador idealmente 0, mas LM317 mínimo ~1.2V.

Circuito

Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1
Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1

Explicação do Circuito

Ideia base: medir corrente de carga e ajustar tensão do regulador até corrente desejada. Corrente medida pela queda de tensão no resistor sensor R5. I = V/R.

Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1
Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1
Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1

Funcionalidade

Sem botões para configurar (evitar fios). Corrente de carga configurada apenas no código (charger.ino):

//*************************** Parâmetros de Carga *****************************************
//*****************************************************************************************
float target_current_mA = 30;           // Corrente de carga mA
float battery_nominal_capacity_mA = 170; // Capacidade nominal bateria mA
float max_time_for_trickle_charge = 6;   // Tempo máximo trickle em minutos
//*****************************************************************************************
//*****************************************************************************************

Corrente de carga até capacidade nominal. Bateria 170mAh → máx 170mA. Mínimo usual C/10 = 17mA.

Ao ligar: verifica se bateria conectada. Se sim, carrega com corrente constante até completa. Término por dV/dt negativo detectado por 5 min. Após completar, muda para trickle C/40. Desconecta após tempo máximo trickle.

Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1

1 = dV/dt | 2 = tempo de carga (min)

Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1
Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1

1 = Tempo carga | 2 = Carga transferida (mAh)

Referências sobre baterias NiMH:

  1. Energizer NiMH App Manual
  2. Battery University - Carregando NiMH

Código

charger.ino (Arduino) - Arquivo Principal

// Carregador 9V NiMH
// Bateria pode ter 6, 7 ou 8 células 1.25V → tensão nominal 7.5–10V
#include 
#include 
#include 
#include 
#include 

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

// Hardware
#define MCP4725_ADDR 0x60
#define DAC_REF_VOLTAGE 5.0
#define CHARGE_RELAY_PIN 2
#define DISCONNECT_CHARGE_RELAY HIGH
#define CONNECT_CHARGE_RELAY LOW
#define CURRENT_SENSING_SESISTOR_PIN A2
#define CURRENT_SENSING_SESISTOR 10.2 // Ohm
#define BATTERY_VOLTAGE_PIN A0
#define R1 5090
#define R2 19910
#define ADC_REF_VOLTAGE 4.89
#define R3 3300.0
#define R4 6800.0
#define AMP_GAIN (1+R4/R3)

#define BATTERY_GOOD_VOLTAGE_THRESHOLD 7.0
#define MAXIMIM_ALLOWED_CURRENT 200
#define MAXIMUM_BATTERY_VOLTAGE 15.0
#define VOLTAGE_STEP 0.001
#define POINTS_FOR_AVERAGING 100
#define MEASURE2MEASURE_US 5000
#define VOLTAGE_LOG_TIME_MIN 30.0
#define MINUTES_TO_LOG 5
#define TRICKLE_CURRENT_RATIO 40

// Estados
#define INITIALIZATION 0
#define NO_BATTERY 5
#define BATTERY_CONNECTED 10
#define BATTERY_VOLTAGE_LOW 15
#define CURRENT_RAMP_UP 20
#define CHARGING 30
#define CURRENT_RAMP_DOWN 32
#define TRICKLE 35
#define END_OF_TRICKLE 37
#define UNEXPECTED_CURRENT_FALL 40
#define UNABLE_TO_REACH_CURRENT 60
#define FULLY_CHARGED 70
#define CURRENT_TOO_HIGH 80
#define REGULATION_FAILED 90
#define OVERCURRENT 100
#define OVERVOLTAGE 101
#define FINAL_STATE 200

unsigned int voltage_readings[POINTS_FOR_AVERAGING], current_sensing_resistor_readings[POINTS_FOR_AVERAGING], averaging_index;
long unsigned int voltage_sum, current_sum;
float regulator_voltage_code, resistor_voltage, csr_voltage_code, regulator_voltage, current_mA, battery_voltage;
float tmp_resistor_voltage, tmp_current_mA, tmp_regulator_voltage, tmp_battery_voltage;
int i, j, charger_state;
short unsigned int last_second, lcd_last_second, log_last_second;
String lcd_last_string1, lcd_last_string2, empty_string = "";
String msg, eoc_line1, eoc_line2;
unsigned char sec_index, min_index;
float sec_log[60], min_log[MINUTES_TO_LOG], last_blf;
float trickle_current_mA;
int total_minutes_average = 0;
elapsedMillis ChargingTimeMillis, TrickleChargingTimeMillis;
float wanted_dac_voltage = 0, last_dac_voltage = 0;

// Mensagens em PROGMEM
const char msg_battery_detected[] PROGMEM = "Bateria detectada";
const char msg_no_battery[] PROGMEM = "Sem bateria";
const char msg_battery_ok[] PROGMEM = "Bateria OK";
const char msg_voltage_too_low[] PROGMEM = "Tensao bateria muito baixa";
const char msg_voltage_too_low_short[] PROGMEM = "V Bateria baixa";
const char msg_ramp_up[] PROGMEM = "Ramp up";
const char msg_charging[] PROGMEM = "Carregando";
const char msg_space[] PROGMEM = " ";
const char msg_ramp_down[] PROGMEM = "Ramp down";
const char msg_trickle_charge[] PROGMEM = "Trickle charge";
const char msg_no_current[] PROGMEM = "Sem corrente";
const char msg_current_unreachable[] PROGMEM = "I inalcancavel";
const char msg_current_unreachable_long[] PROGMEM = "Nao consegue atingir corrente desejada";
const char msg_completed[] PROGMEM = "Completo";
const char msg_charge[] PROGMEM = "Carga";
const char msg_high_current[] PROGMEM = "Corrente alta";
const char msg_regulation_fault[] PROGMEM = "Falha regulacao";
const char msg_overcurrent[] PROGMEM = "Corrente excessiva";
const char msg_overvoltage[] PROGMEM = "Tensao excessiva";
const char msg_trickle_completed[] PROGMEM = "Trickle finalizado";

struct mytime {
  unsigned char hours;
  unsigned char minutes;
  unsigned int total_minutes;
} elapsed_time;

void setup() {
  pinMode(CHARGE_RELAY_PIN, OUTPUT);
  disconnect_charging_circuit();
  Wire.begin();
  dac_write_voltage(0);
  Serial.begin(115200);
  last_second = second();
  lcd_last_second = second();
  log_last_second = second();
  Timer1.initialize(MEASURE2MEASURE_US);
  Timer1.attachInterrupt(read_hw);
  averaging_index = 0;
  sec_index = 0; min_index = 0;
  charger_state = 0;
  wanted_dac_voltage = 0;
  last_blf = 1.0;
  trickle_current_mA = battery_nominal_capacity_mA / TRICKLE_CURRENT_RATIO;
  lcd.begin(16, 2);
  lcd.backlight();
  lcd.clear();
  update_lcd(F("Ligando..."), empty_string);
  delay(1000);
}

float log_battery_voltage() {
  if (log_last_second == second()) return last_blf;
  log_last_second = second();
  sec_log[sec_index] = battery_voltage;
  if (sec_index < 59) sec_index++;
  else {
    if (min_index >= MINUTES_TO_LOG) min_index = 0;
    sec_index = 0;
    float sum_v = 0;
    for (i = 0; i < 60; i++) sum_v += sec_log[i];
    float min_average = sum_v / 60.0;
    for (i = 1; i < MINUTES_TO_LOG; i++) min_log[i-1] = min_log[i];
    min_log[MINUTES_TO_LOG-1] = min_average;
    last_blf = best_linear_fit(min_log);
    min_index++;
    total_minutes_average++;
  }
  return last_blf;
}

main.ino (Arduino)

void loop() {
  String msg1;
  switch(charger_state) {
    case INITIALIZATION:
      disconnect_charging_circuit();
      dac_write_voltage(0);
      wanted_dac_voltage = 0;
      delay(100);
      read_status();
      if (battery_voltage > 0.1) {
        charger_state = BATTERY_CONNECTED;
        update_lcd(M2S(msg_battery_detected), empty_string);
        Serial.println(M2S(msg_battery_detected));
        delay(2000);
      } else {
        Serial.println(M2S(msg_no_battery));
        update_lcd(M2S(msg_no_battery), construct_status_string());
        charger_state = NO_BATTERY;
        delay(1000);
      }
      break;
    case NO_BATTERY:
      read_status();
      if (battery_voltage > 0.1) {
        charger_state = BATTERY_CONNECTED;
        Serial.println(M2S(msg_battery_detected));
        update_lcd(M2S(msg_battery_detected), construct_status_string());
        delay(1500);
      } else {
        update_lcd(M2S(msg_no_battery), construct_status_string());
        delay(1100);
      }
      break;
    case BATTERY_CONNECTED:
      dac_write_voltage(0);
      wanted_dac_voltage = 0;
      delay(100);
      read_status();
      if (battery_voltage > BATTERY_GOOD_VOLTAGE_THRESHOLD) {
        charger_state = CURRENT_RAMP_UP;
        update_lcd(M2S(msg_battery_ok), construct_status_string());
        Serial.println(M2S(msg_battery_ok));
        delay(2000);
        wanted_dac_voltage = get_approximated_dac_voltage(battery_voltage);
        connect_charging_circuit();
        delay(200);
      } else {
        charger_state = BATTERY_VOLTAGE_LOW;
        Serial.println(M2S(msg_voltage_too_low));
        update_lcd(M2S(msg_voltage_too_low_short), construct_status_string());
        delay(1000);
      }
      break;
    case BATTERY_VOLTAGE_LOW:
      update_lcd(M2S(msg_voltage_too_low_short), construct_status_string());
      Serial.println(M2S(msg_voltage_too_low));
      charger_state = FINAL_STATE;
      break;
    case CURRENT_RAMP_UP:
      read_status();
      update_lcd(M2S(msg_ramp_up), construct_status_string());
      delay(50);
      if (current_mA < target_current_mA) {
        if (wanted_dac_voltage < 4.99) {
          wanted_dac_voltage += VOLTAGE_STEP;
          dac_write_voltage(wanted_dac_voltage);
        } else charger_state = UNABLE_TO_REACH_CURRENT;
      } else {
        charger_state = CHARGING;
        ChargingTimeMillis = 0;
      }
      break;
    case CHARGING:
      delay(200);
      read_status();
      if (current_mA < 1.0) charger_state = UNEXPECTED_CURRENT_FALL;
      if (battery_voltage > MAXIMUM_BATTERY_VOLTAGE) charger_state = OVERVOLTAGE;
      if (abs(current_mA - target_current_mA) > 0.2) {
        if (current_mA < target_current_mA) {
          if (wanted_dac_voltage < 4.99) wanted_dac_voltage += VOLTAGE_STEP;
          else charger_state = UNABLE_TO_REACH_CURRENT;
        } else {
          if (wanted_dac_voltage > 0.01) wanted_dac_voltage -= VOLTAGE_STEP;
          else charger_state = CURRENT_TOO_HIGH;
        }
      }
      if (abs(current_mA - target_current_mA) > 5) charger_state = REGULATION_FAILED;
      dac_write_voltage(wanted_dac_voltage);
      if (total_minutes_average < MINUTES_TO_LOG) msg = String(M2S(msg_charging) + M2S(msg_space) + "---");
      else msg = String(M2S(msg_charging) + M2S(msg_space) + String(last_blf, 3));
      update_lcd(msg, construct_status_string());
      log_battery_voltage();
      if (last_blf < 0) charger_state = FULLY_CHARGED;
      break;
    case CURRENT_RAMP_DOWN:
      read_status();
      update_lcd(M2S(msg_ramp_down), construct_status_string());
      Serial.println(M2S(msg_ramp_down));
      delay(50);
      if (current_mA > trickle_current_mA) {
        if (wanted_dac_voltage > VOLTAGE_STEP) {
          wanted_dac_voltage -= VOLTAGE_STEP;
          dac_write_voltage(wanted_dac_voltage);
        } else charger_state = CURRENT_TOO_HIGH;
      } else {
        charger_state = TRICKLE;
        TrickleChargingTimeMillis = 0;
        Serial.println(M2S(msg_trickle_charge));
      }
      break;
    case TRICKLE:
      delay(200);
      read_status();
      if (current_mA < 0.2) charger_state = UNEXPECTED_CURRENT_FALL;
      if (battery_voltage > MAXIMUM_BATTERY_VOLTAGE) charger_state = OVERVOLTAGE;
      if (abs(current_mA - trickle_current_mA) > 0.2) {
        if (current_mA < trickle_current_mA) {
          if (wanted_dac_voltage < 4.99) wanted_dac_voltage += VOLTAGE_STEP;
          else charger_state = UNABLE_TO_REACH_CURRENT;
        } else {
          if (wanted_dac_voltage > 0.01) wanted_dac_voltage -= VOLTAGE_STEP;
          else charger_state = CURRENT_TOO_HIGH;
        }
      }
      if (abs(current_mA - trickle_current_mA) > 5) charger_state = REGULATION_FAILED;
      dac_write_voltage(wanted_dac_voltage);
      if ((second() % 8) < 4) update_lcd(M2S(msg_trickle_charge), construct_status_string());
      else update_lcd(eoc_line1, eoc_line2);
      elapsed_time = mills2time(TrickleChargingTimeMillis);
      if (elapsed_time.total_minutes > max_time_for_trickle_charge) {
        update_lcd(eoc_line1, eoc_line2);
        charger_state = END_OF_TRICKLE;
        disconnect_charging_circuit();
      }
      break;
    case END_OF_TRICKLE:
      if ((second() % 8) < 4) update_lcd(M2S(msg_trickle_completed), construct_status_string());
      else update_lcd(eoc_line1, eoc_line2);
      break;
    case UNEXPECTED_CURRENT_FALL:
      Serial.println(F("Corrente caiu inesperadamente"));
      disconnect_charging_circuit();
      wanted_dac_voltage = 0;
      update_lcd(M2S(msg_no_current), construct_status_string());
      charger_state = FINAL_STATE;
      delay(1000);
      break;
    case UNABLE_TO_REACH_CURRENT:
      Serial.println(M2S(msg_current_unreachable_long));
      disconnect_charging_circuit();
      wanted_dac_voltage = 0;
      dac_write_voltage(0);
      delay(1000);
      update_lcd(M2S(msg_current_unreachable), construct_status_string());
      charger_state = FINAL_STATE;
      break;
    case FULLY_CHARGED:
      elapsed_time = mills2time(ChargingTimeMillis);
      int charge_mAh = calculate_charge(ChargingTimeMillis);
      msg = String(M2S(msg_completed) + M2S(msg_space) + construct_time_string(elapsed_time));
      msg1 = String(M2S(msg_charge) + M2S(msg_space) + String(charge_mAh) + "mAh");
      eoc_line1 = msg; eoc_line2 = msg1;
      update_lcd(msg, msg1);
      Serial.println(msg);
      delay(3000);
      charger_state = CURRENT_RAMP_DOWN;
      break;
    case CURRENT_TOO_HIGH:
      Serial.println(F("Nao consegue baixar corrente alvo"));
      disconnect_charging_circuit();
      wanted_dac_voltage = 0;
      dac_write_voltage(0);
      update_lcd(M2S(msg_high_current), construct_status_string());
      delay(1000);
      charger_state = FINAL_STATE;
      break;
    case REGULATION_FAILED:
      Serial.println(M2S(msg_regulation_fault));
      disconnect_charging_circuit();
      wanted_dac_voltage = 0;
      dac_write_voltage(0);
      update_lcd(M2S(msg_regulation_fault), construct_status_string());
      delay(1000);
      charger_state = FINAL_STATE;
      break;
    case OVERCURRENT:
      disconnect_charging_circuit();
      Serial.println(M2S(msg_overcurrent));
      wanted_dac_voltage = 0;
      dac_write_voltage(wanted_dac_voltage);
      update_lcd(M2S(msg_overcurrent), construct_status_string());
      delay(1000);
      charger_state = FINAL_STATE;
      break;
    case OVERVOLTAGE:
      disconnect_charging_circuit();
      Serial.println(M2S(msg_overvoltage));
      wanted_dac_voltage = 0;
      dac_write_voltage(wanted_dac_voltage);
      update_lcd(M2S(msg_overvoltage), construct_status_string());
      delay(1000);
      charger_state = FINAL_STATE;
      break;
    case FINAL_STATE:
      delay(10000);
      break;
    default:
      wanted_dac_voltage = 0;
      charger_state = 0;
  }
  read_status();
  if (last_second != second()) {
    Serial.print(current_mA); Serial.print(","); Serial.println(battery_voltage);
    last_second = second();
  }
}

hw.ino (Arduino)

float get_approximated_dac_voltage(float vbat) {
  float offset_voltage = 1.2;
  float adc_voltage = (vbat - offset_voltage) / AMP_GAIN - 0.5;
  if (adc_voltage < 0) adc_voltage = 0;
  return adc_voltage;
}

int voltage_to_code(float voltage) {
  return 4095.0 / DAC_REF_VOLTAGE * voltage;
}

void dac_write(int code) {
  Wire.beginTransmission(MCP4725_ADDR);
  Wire.write(64);
  Wire.write(code >> 4);
  Wire.write((code & 15) << 4);
  Wire.endTransmission();
}

void read_status() {
  voltage_sum = 0; current_sum = 0;
  for (i = 0; i < POINTS_FOR_AVERAGING; i++) {
    voltage_sum += voltage_readings[i];
    current_sum += current_sensing_resistor_readings[i];
  }
  regulator_voltage_code = (float)voltage_sum / POINTS_FOR_AVERAGING;
  csr_voltage_code = (float)current_sum / POINTS_FOR_AVERAGING;
  regulator_voltage = regulator_voltage_code * (ADC_REF_VOLTAGE / 1023.0) * (R1 + R2) / R1;
  resistor_voltage = csr_voltage_code * (ADC_REF_VOLTAGE / 1023.0);
  current_mA = resistor_voltage / CURRENT_SENSING_SESISTOR * 1000;
  battery_voltage = regulator_voltage - resistor_voltage;
}

void dac_write_voltage(float voltage) {
  if (last_dac_voltage == voltage) return;
  last_dac_voltage = voltage;
  dac_write(voltage_to_code(voltage));
}

void read_hw() {
  voltage_readings[averaging_index] = analogRead(BATTERY_VOLTAGE_PIN);
  current_sensing_resistor_readings[averaging_index] = analogRead(CURRENT_SENSING_SESISTOR_PIN);
  tmp_resistor_voltage = current_sensing_resistor_readings[averaging_index] * (ADC_REF_VOLTAGE / 1023.0);
  tmp_current_mA = tmp_resistor_voltage / CURRENT_SENSING_SESISTOR * 1000;
  tmp_regulator_voltage = voltage_readings[averaging_index] * (ADC_REF_VOLTAGE / 1023.0) * (R1 + R2) / R1;
  tmp_battery_voltage = tmp_regulator_voltage - tmp_resistor_voltage;
  averaging_index++;
  if (averaging_index >= POINTS_FOR_AVERAGING) averaging_index = 0;
  if (tmp_battery_voltage > MAXIMUM_BATTERY_VOLTAGE) {
    disconnect_charging_circuit();
    charger_state = OVERVOLTAGE;
  }
  if (tmp_current_mA > MAXIMIM_ALLOWED_CURRENT) {
    disconnect_charging_circuit();
    charger_state = OVERCURRENT;
  }
}

void disconnect_charging_circuit() { digitalWrite(CHARGE_RELAY_PIN, DISCONNECT_CHARGE_RELAY); }
void connect_charging_circuit() { digitalWrite(CHARGE_RELAY_PIN, CONNECT_CHARGE_RELAY); }

lcd.ino (Arduino)

void update_lcd(String first_line, String second_line) {
  if (lcd_last_string1 != first_line) {
    lcd.clear();
    lcd.setCursor(0, 0); lcd.print(first_line);
    lcd_last_string1 = first_line;
    lcd.setCursor(0, 1); lcd.print(second_line);
    lcd_last_string2 = second_line;
  }
  if (lcd_last_second != second()) {
    if (lcd_last_string2 != second_line) {
      lcd.setCursor(0, 1); lcd.print(second_line);
      lcd_last_string2 = second_line;
    }
  }
  lcd_last_second = second();
}

String construct_status_string() {
  String v = (battery_voltage < 10) ? String(battery_voltage, 2) : String(battery_voltage, 1);
  String i = (current_mA < 10) ? String(current_mA, 2) : String(current_mA, 1);
  mytime elapsed; String msg, msg_time;
  switch (charger_state) {
    case CHARGING: elapsed = mills2time(ChargingTimeMillis); break;
    case TRICKLE: elapsed = mills2time(TrickleChargingTimeMillis); break;
  }
  if (charger_state == CHARGING || charger_state == TRICKLE) {
    if (elapsed.total_minutes < 10) msg_time = String(elapsed.total_minutes) + " ";
    else if (elapsed.total_minutes < 100) msg_time = String(elapsed.total_minutes) + " ";
    else msg_time = String(elapsed.total_minutes);
  }
  switch (charger_state) {
    case CHARGING: msg = v + "V " + i + "mA " + msg_time; break;
    case TRICKLE: msg = v + "V " + i + "mA " + msg_time; break;
    default: msg = v + "V " + i + "mA";
  }
  msg.replace("-", "");
  return msg;
}

String construct_time_string(mytime timeinfo) {
  return String(timeinfo.hours, DEC) + ":" + String(timeinfo.minutes, DEC);
}

calculations.ino (Arduino)

float best_linear_fit(float y[MINUTES_TO_LOG]) {
  float sx = 0, sy = 0, sxx = 0, sxy = 0;
  for (i = 0; i < MINUTES_TO_LOG; ++i) {
    sx += (float)i;
    sy += y[i];
    sxx += (float)i * (float)i;
    sxy += (float)i * y[i];
  }
  float delta = MINUTES_TO_LOG * sxx - sx * sx;
  float slope = (MINUTES_TO_LOG * sxy - sx * sy) / delta;
  return slope;
}

mytime mills2time(unsigned long time_mills) {
  mytime result;
  result.total_minutes = time_mills / 1000 / 60;
  result.hours = result.total_minutes / 60;
  result.minutes = result.total_minutes % 60;
  return result;
}

int calculate_charge(unsigned long time_mills) {
  unsigned int elapsed_sec = time_mills / 1000;
  float elapsed_hours = elapsed_sec / 60.0 / 60.0 * target_current_mA;
  return (int)elapsed_hours;
}

String M2S(const char signMessage[]) {
  int k, len = strlen_P(signMessage);
  String mess;
  for (k = 0; k < len; k++) mess += (char)pgm_read_byte_near(signMessage + k);
  return mess;
}

Biblioteca New LiquidCrystal

Usada para o display LCD I2C YwRobot. (Download apenas)

Esquemáticos

Carregador Avançado de Bateria 9V NiMH – Inteligente, Preciso e Confiável – Versão 1

charger_AgK96zxw2T.zip

Processo de manufatura

  1. Alerta Inteligente de Sede para Plantas – Sensor DIY para Hidratação Ideal
  2. Relógio de Palavras em Italiano com Arduino Nano, NeoPixel, LCD e RTC
  3. Economize Pinos do Arduino: Conecte Teclado 4x3 com Apenas Três Conexões
  4. Medidor kWh Confiável com Sigfox e Arduino MKR Fox 1200
  5. Monitor de Temperatura com Bluetooth usando Arduino Nano
  6. Fechadura por Gestos Sem Toque: Acesso Seguro com APDS-9960 e Arduino
  7. Companion IC: Relógio, Ohmímetro, Capacímetro e Testador de Diodos com Arduino Nano
  8. Kit Adaptador USB-MIDI para Arduino Micro/Leonardo – Lista Completa de Peças
  9. Circuito de Entrada Analógica Isolada para Arduino
  10. Teste de Tempo de Reação Preciso com Arduino UNO