Cargador Avanzado 9V NiMH – Inteligente, Preciso y Fiable – Versión 1
Componentes y Suministros
| Arduino UNO | 1 |
| SparkFun I2C DAC Breakout - MCP4725 | 1 |
| Regulador Lineal con Salida Ajustable (LM317) | 1 |
| Texas Instruments Op-Amp Dual Uso General (LM358) | 1 |
| Resistencia 6.8k ohm | 2 |
| Resistencia 3.3k ohm | 1 |
| Resistencia 5.1k ohm | 1 |
| Resistencia 1k ohm | 1 |
| Resistencia 10 ohm | 1 |
| Relé (genérico) | 1 |
| Resistencia 20k ohm | 1 |
| SparkFu Breadboard Power Supply 5V/3.3V | 1 |
| Fuente de Alimentación 17V | 1 |
| YwRobot I2C SERIAL LCD 1602 MODULE | 1 |
Sobre el Proyecto
Buscaba un cargador inteligente que pudiera cargar una batería 9V NiMH en un par de horas y no encontré ninguno. Además, todos los cargadores que hallé eran realmente "tontos": corriente de carga desconocida y sin función para terminar la carga cuando la batería está llena. El problema es que pueden sobrecargar la batería y reducir drásticamente su vida útil. Así que decidí crear un cargador "inteligente".
En esta primera versión mantengo lo simple: carga con corriente constante, terminación automática al completar la carga, carga de mantenimiento (trickle), medición de carga transferida.
En la próxima versión añadiré funciones adicionales como descarga, medición de capacidad y ciclos.
Advertencia: Cargar baterías con alta corriente puede causar explosión o incendio. No dejes el cargador desatendido. Tampoco intentes cargar baterías no recargables (alcalinas). Este cargador se ha probado solo con baterías NiMH (y aún así lo usas bajo tu propio riesgo; no tengo responsabilidad por daños causados por fallos de diseño o código). Cargar otros tipos de baterías requerirá modificación del código.
Teoría
Datos útiles para entender los parámetros necesarios del cargador.
- C = corriente igual a la capacidad nominal de la batería
- Al cargar a tasa C, el voltaje de una celda puede alcanzar 1.6V (más en baterías viejas)
- Voltaje nominal de celda: 1.2V, pero celda cargada totalmente tiene voltaje de circuito abierto hasta 1.5V
- Tasa de carga de mantenimiento (trickle) recomendada: menos de 0.025 C (C/40) tras carga completa
- Dos opciones para cargar NiMH:
- Carga rápida: corriente 0.5C-1C. Estado de carga monitorizado y terminado por dV/dt (tasa de cambio de voltaje) o dT/dt (tasa de cambio de temperatura)
- Carga lenta: corriente 0.1C. Tiempo 14-16 horas. Terminación por temporizador. dT/dt imposible para corrientes bajas. dV/dt puede no ser fiable por debajo de 0.5C según literatura.

Parámetros Básicos del Circuito de Carga
Una batería 9V suele tener 7 celdas en serie, pero puede tener 6 u 8. El regulador debe proporcionar voltaje de carga al menos hasta 8×1.6=12.8V. La caída del regulador LM317 es hasta 2V, así que la alimentación debe ser ~15V (sin contar la caída en la resistencia de detección de corriente).
Para corriente máxima de 200mA y resistencia de detección de 10 Ohm, caída adicional es 2V, así que se necesitan 17V de alimentación.
Una celda totalmente descargada puede tener voltaje muy bajo, incluso negativo. El voltaje mínimo del regulador idealmente sería 0, pero con LM317 puede ser tan bajo como 1.2V.
Circuito


Explicación del Circuito
La idea básica es medir la corriente de carga y ajustar el voltaje del regulador hasta alcanzar la corriente deseada. La corriente se mide midiendo la caída de voltaje en la resistencia de detección R5: I=V/R.
El SparkFun I2C DAC Breakout - MCP4725 (convertidor digital-analógico de 12 bits) controla el voltaje. Su salida se configura vía I2C entre 0 y 5V. Como necesitamos ajustar voltaje en rango mayor (0-15V), usamos el amplificador operacional LM358 para amplificar la salida del DAC. La ganancia del op-amp la fijan R4 y R3: Ganancia = 1 + R4/R3 = 1 + 6800/3300 = 3.06, así que la salida del op-amp es aprox. 0-15V.
La corriente máxima de salida del LM358 es 50mA, así que usamos el regulador de voltaje ajustable LM317 para controlar corriente mayor. La salida del op-amp se conecta al terminal ADJ del LM317. El LM317 mantiene 1.2V entre ADJ y OUT, así que el voltaje real en la batería se configura entre 1.2 y 16.2V. El LM317 necesita mínimo 3.5mA para mantener regulación, así que la resistencia de 1kOhm R6 asegura regulación si no hay batería conectada. El condensador C1 filtra el voltaje de salida y mejora la estabilidad del LM317.
El voltaje se mide en dos puntos:
- Resistencia R5 conectada a pin A2 del Arduino. Se mide el voltaje en la resistencia y se calcula la corriente de carga: Icharging = V/R
- El voltaje en la batería puede llegar a 16.2V, así que un divisor resistivo R1,R2 lo reduce por debajo de 5V, permitido por Arduino. La salida del divisor va al pin A0. Con R1=5.1k y R2=20k, Vout = Vin/(20000+5100)×5100 = 0.2, es decir, el voltaje de batería dividido por 5.
El relé desconecta la batería del circuito de carga. Usé el que se ve en la foto, pero sirve cualquier relé con control a 5V. Es más seguro conectar la batería a contactos normalmente abiertos (NO) del relé.
Usé el módulo YwRobot I2C SERIAL LCD 1602 para mostrar el estado del cargador, pero cualquier otro módulo LCD I2C sirve. Parece que el módulo YwRobot no es compatible con la librería estándar LiquidCrystal_I2C, así que usé la librería New LiquidCrystal. Si usas otro módulo LCD, tendrás que cambiar esta línea:
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // set the LCD address to 0x27 for a 16 chars and 2 line display
Para alimentar el DAC y el LCD usé la SparkFun Breadboard Power Supply 5V/3.3V. Probablemente también valga la salida 5V del Arduino.
También necesitas suministrar 17V al circuito de carga. Si no tienes fuente, puedes usar un conversor DC/DC ajustable step-up como este:



Funcionalidad
No quería muchos cables, así que no hay botones para configurar la carga. La corriente de carga se configura solo en el código. Debes establecer la corriente deseada en charger.ino:
//*************************** Parámetros de Carga *****************************************
//*****************************************************************************************
float target_current_mA=30; // Corriente de carga mA
float battery_nominal_capacity_mA=170; // Capacidad nominal batería mA
float max_time_for_trickle_charge=6; // Tiempo máximo carga mantenimiento en minutos
//*****************************************************************************************
//*****************************************************************************************
target_current_mA= corriente de carga constantemax_time_for_trickle_charge= minutos máximos para carga de mantenimiento (hasta 600 = 10h)battery_nominal_capacity_mA= capacidad de batería para calcular corriente trickle
Generalmente la corriente de carga puede ser hasta la capacidad nominal. Para batería de 170mAh, máximo 170mA. Mínimo suele ser C/10 = 17mA para 170mAh.
Al encender, el cargador verifica si hay batería conectada. Si la hay, carga con corriente constante hasta completar. La terminación detecta dV/dt negativo durante 5 minutos. Tras completar, cambia a carga trickle con corriente C/40. Se desconecta solo tras el tiempo máximo de trickle.

1 - dV/dt
2 - tiempo de carga en minutos


1 - Tiempo de carga
2 - Carga transferida a la batería
Información adicional sobre baterías NiMH:
Código
charger.ino (Arduino) - Archivo Principal
//THis for charging 9V NiMH battery
//Battery can have have 6,7 or 8 1.25V cells
//This makes nominal voltage between 7.5 and 10V
#include
#include
#include
#include
#include
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);
// Hardware definitions
#define MCP4725_ADDR 0x60 // DAC address
#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 // Low side resistor of voltage sensing divider
#define R2 19910 // High side resistor of voltage sensing divider
#define ADC_REF_VOLTAGE 4.89 // Arduino real supply voltage or AREF voltage
#define R3 3300.0
#define R4 6800.0
#define AMP_GAIN (1+R4/R3)
// Various definitions
#define BATTERY_GOOD_VOLTAGE_THRESHOLD 7.0
#define MAXIMIM_ALLOWED_CURRENT 200 // Maximum allowed battery current mA (hard cut-off)
#define MAXIMUM_BATTERY_VOLTAGE 15.0 // Maximum allowed battery voltage V (hard cut-off)
#define VOLTAGE_STEP 0.001 // Step for voltage regulation
#define POINTS_FOR_AVERAGING 100 // How many points taken for averaging
#define MEASURE2MEASURE_US 5000 // Time between measurements in microseconds
#define VOLTAGE_LOG_TIME_MIN 30.0 // Time in minutes to save voltage for dV/dT cutoff
#define MINUTES_TO_LOG 5
#define TRICKLE_CURRENT_RATIO 40
// Charger states
#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;
String 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;
float last_dac_voltage = 0;
// Messages stored in PROGMEM
const char msg_battery_detected[] PROGMEM = "Battery detected";
const char msg_no_battery[] PROGMEM = "No battery";
const char msg_battery_ok[] PROGMEM = "Battery ok";
const char msg_voltage_too_low[] PROGMEM = "Battery voltage too low";
const char msg_voltage_too_low_short[] PROGMEM = "V Battery low";
const char msg_ramp_up[] PROGMEM = "Ramp up";
const char msg_charging[] PROGMEM = "Charging";
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 = "No current";
const char msg_current_unreachable[] PROGMEM = "I unreachable";
const char msg_current_unreachable_long[] PROGMEM = "Unable to reach desired current";
const char msg_completed[] PROGMEM = "Completed";
const char msg_charge[] PROGMEM = "Charge";
const char msg_high_current[] PROGMEM = "High current";
const char msg_regulation_fault[] PROGMEM = "Regulation fault";
const char msg_overcurrent[] PROGMEM = "Current too high";
const char msg_overvoltage[] PROGMEM = "Voltage too high";
const char msg_trickle_completed[] PROGMEM = "Trickle finished";
// Charging parameters (EDIT THESE)
float target_current_mA = 30;
float battery_nominal_capacity_mA = 170;
float max_time_for_trickle_charge = 6;
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("Power up..."), empty_string);
delay(1000);
}
float log_battery_voltage() {
if (log_last_second == second()) return last_blf;
else 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("Current unexpectedly fell"));
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(wanted_dac_voltage);
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("Unable to lower current to target"));
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) {
int code = 4095.0 / DAC_REF_VOLTAGE * voltage;
return code;
}
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(void) {
String v, i;
if (battery_voltage < 10) v = String(battery_voltage, 2);
else v = String(battery_voltage, 1);
if (current_mA < 10) i = String(current_mA, 2);
else i = 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:
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.0, sy = 0.0, sxx = 0.0, sxy = 0.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 int 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;
len = strlen_P(signMessage);
String mess;
char myChar;
for (k = 0; k < len; k++) {
mess += (char)pgm_read_byte_near(signMessage + k);
}
return mess;
}
Librería New LiquidCrystal usada en este proyecto
Sin vista previa (solo descarga).
Esquemáticos
charger_AgK96zxw2T.zip
Proceso de manufactura
- Alerta de Sed Inteligente para Plantas: Sensor DIY para Riego Óptimo
- Reloj de Palabras en Italiano con Arduino Nano, NeoPixel, LCD y RTC
- Ahorra Pines en Arduino: Conecta Teclado 4x3 con Solo 3 Conexiones Analógicas
- Medidor kWh Sigfox Fiable con Arduino MKR Fox 1200
- Monitor de temperatura con Bluetooth usando Arduino Nano
- Cerradura por Gestos Sin Contacto: Acceso Seguro con APDS-9960 y Arduino
- Kit Compañero Arduino Nano: Componentes y Herramientas Esenciales para Electrónica DIY
- Kit Adaptador USB MIDI para Arduino Micro / Leonardo: Lista Completa de Componentes
- Circuito de entrada analógica aislada para Arduino
- Visualizador Espectro Audio RGB 32 Bandas - Matriz LED Arduino
- Test de Precisión de Tiempo de Reacción con Arduino UNO