Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Manufacturing Technology >> Productieproces

Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1

Onderdelen en Benodigdheden

Onderdeel Aantal Opmerking
Arduino UNO 1
SparkFun I2C DAC Breakout - MCP4725 1
Lineaire Regelaar met Instelbare Output 1
Texas Instruments Algemeen Doel Dubbele Op-Amp (LM358) 1
Weerstand 6.8k ohm 2
Weerstand 3.3k ohm 1
Weerstand 5.1k ohm 1
Weerstand 1k ohm 1
Weerstand 10 ohm 1
Relais (standaard) 1
Weerstand 20k ohm 1
SparkFun Breadboard Voeding 5V/3.3V 1
Voeding 17V 1
YwRobot I2C SERIEEL LCD 1602 MODULE 1

Over dit Project

Ik zocht een slimme lader die een 9V NiMH batterij in een paar uur kan laden en vond er geen. Bovendien waren alle laders die ik vond echt "dom". Laadstroom onbekend en geen functie om het laden te beëindigen nadat de batterij vol is. Het probleem met zulke laders is dat ze de batterij kunnen overladen en de levensduur aanzienlijk verkorten. Dus besloot ik een "slimme" lader te maken.

De eerste versie houd ik simpel, dus het stelt basisdingen toe als laden met constante stroom, automatische laadbeëindiging na volledige lading, drijflading (trickle charge), en meting van de overgebrachte lading.

In de volgende versie zal ik een paar extra nuttige functies toevoegen zoals ontladen, capaciteitsmeting en cycleren.

Waarschuwing: Laden van een batterij met hoge stroom kan batterijexplosie of brand veroorzaken. Laat de lader nooit onbewaakt. Probeer ook geen batterijen te laden die niet bedoeld zijn om te laden, zoals Alkali. Deze lader is alleen getest met NiMH batterijen (en nog steeds gebruik je het op eigen risico en ik heb absoluut geen verantwoordelijkheid als er schade ontstaat door bugs in ontwerp of code). Het laden van andere batterijtypen vereist codewijzigingen.

Theorie

Enkele nuttige feiten om te onthouden die helpen de benodigde parameters van de lader te begrijpen.

C - stroom gelijk aan de nominale capaciteit van de batterij

Wanneer geladen met C-tempo kan de celspanning 1.6V bereiken. Deze spanning kan hoger zijn voor oude batterijen.

Nominale spanning van een enkele cel is 1.2V, maar een volledig geladen cel heeft een open-circuitspanning tot 1.5 volt.

Drijfladingsstroom van minder dan 0.025 C (C/40) wordt aanbevolen nadat de batterij volledig geladen is.

Er zijn in het algemeen twee opties om een NiMH batterij te laden:

  1. Snel laden. Laadstroom 0.5C-1C. Laadtoestand moet bewaakt en beëindigd worden door dV/dt (spanningsveranderingsgraad) of dT/dt (temperatuurveranderingsgraad)
  2. Langzaam laden. Laadstroom 0.1C. Laadtijd 14-16 uur. Laadbeëindiging door timer. dT/dt beëindiging onmogelijk voor lage stromen. dV/dt beëindiging kan onbetrouwbaar zijn voor stromen onder 0.5C volgens literatuur.
Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1

Basale Parameters van het Laadcircuit

Een 9V batterij heeft meestal 7 serieel aangesloten cellen, maar in sommige gevallen kan het 6 of 8 cellen zijn. De spanningsregelaar moet in staat zijn om laadspanning te leveren tot minimaal 8*1.6=12.8V. Dropout spanning van LM317 regelaar tot 2V, dus voedingspanning moet ~15V zijn (dit houdt nog geen rekening met spanningsval op de stroomsensor weerstand).

Voor maximale laadstroom van 200mA en stroomsensor weerstand van 10 Ohm is de extra val op de stroomsensor weerstand 2V, dus 17V voedingspanning nodig.

Een volledig ontladen cel kan een zeer lage, zelfs negatieve spanning hebben. Minimale spanning van de regelaar ideaal 0, maar met LM317 kan het zo laag als 1.2V zijn.

Circuit

Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1
Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1

Circuit Uitleg

Het basisidee is om de laadstroom te meten en de spanning van de regelaar aan te passen tot de gewenste stroom bereikt is. Stroom gemeten door de spanningsval over de stroomsensor weerstand R5. I=V/R.

SparkFun I2C DAC Breakout - MCP4725 - 12-bit digitaal-naar-analoog converter gebruikt om de spanning te besturen. Output spanning kan via I2C tussen 0 en 5V geconfigureerd worden. Omdat we de spanning in een breder bereik moeten kunnen aanpassen, van 0 tot 15V, wordt operationele versterker LM358 gebruikt om de output spanning van de DAC te versterken. Versterking van de op-amp ingesteld door weerstanden R4 en R3. Gain=1+R4/R3=1+6800/3300=3.06 dus output spanning van op-amp ongeveer 0 tot 15V.

Maximale output stroom van LM358 is 50mA, dus LM317 instelbare spanningsregelaar gebruikt om hogere stroom te besturen. Output van op-amp verbonden met ADJ terminal van LM317. LM317 handhaaft 1.2V tussen ADJ en OUT terminals, dus de werkelijke spanning op de batterij kan geconfigureerd worden tussen 1.2 en 16.2V. LM317 heeft minimaal 3.5mA stroom nodig om regeling te handhaven. Dus 1kOhm weerstand R6 gebruikt om regeling te waarborgen als batterij niet aangesloten is. Condensator C1 gebruikt om output spanning te filteren en stabiliteit van LM317 te verbeteren.

Spanning gemeten op twee verschillende punten.

  1. Weerstand R5 verbonden met pin A2 van Arduino. Spanning op weerstand gemeten en dan laadstroom berekend als Icharging=V/R
  2. Spanning op batterij kan tot 16.2V zijn, dus resistieve deler R1,R2 gebruikt om spanning onder 5V te brengen, toegestaan door Arduino. Output van deler verbonden met pin A0 van Arduino. Voor R1=5.1k Ohm en R2=20kOhm Vout=Vin/(20000+5100)*5100=0.2 Dus batterijspanning gedeeld door 5.

Relais gebruikt om batterij van laadcircuit te scheiden. Je ziet op de foto welk relais ik gebruikte, maar in het algemeen kan elk relais met 5V besturing gebruikt worden. Het is veiliger om de batterij aan de normaal open contacten van het relais te verbinden.

Ik gebruikte YwRobot I2C SERIEEL LCD 1602 MODULE om status van lader weer te geven, maar elke andere I2C gestuurde LCD module kan gebruikt worden. Het lijkt alsof YwRobot LCD module niet ondersteund wordt door standaard LiquidCrystal_I2C bibliotheek, dus ik gebruikte New LiquidCrystal bibliotheek. Als je een andere LCD module gebruikt, moet je deze regel aanpassen:

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // zet LCD adres op 0x27 voor 16 chars en 2 regels display

Om de DAC en LCD te voeden gebruikte ik SparkFun Breadboard Voeding 5V/3.3V. Het is waarschijnlijk ook ok om 5V van het Arduino bord te gebruiken.

Je hebt ook 17V nodig voor het laadcircuit. Als je geen voeding hebt, kun je een instelbare DC/DC converter gebruiken zoals deze:

DC-DC Adjustable Step-up Boost Power Converter Module XL6009

Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1
Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1
Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1

Functionaliteit

Ik wilde niet veel draden, dus er zijn geen knoppen om het laden te configureren. Laadstroom geconfigureerd alleen in code. Je moet de gewenste laadstroom instellen in charger.ino

//*************************** Laadparameters *****************************************
//*****************************************************************************************
float target_current_mA=30; // Laadstroom mA
float battery_nominal_capacity_mA=170; // Nominale capaciteit van batterij mA
float max_time_for_trickle_charge=6; // Maximale drijfladtijd in minuten
//*****************************************************************************************
//*****************************************************************************************

target_current_mA - constante laadstroom

max_time_for_trickle_charge - maximum aantal minuten voor drijfladen, kan ingesteld worden tot 600 (10u)

battery_nominal_capacity_mA - batterijcapaciteit gebruikt om drijfstroom te berekenen

Over het algemeen kan de laadstroom tot nominale capaciteit zijn. Voor batterij met nominale capaciteit van 170mAh is maximale laadstroom 170mA. Minimale laadstroom meestal C/10 - 17mA voor 170mAh batterij.

Na opstarten controleert de lader of batterij aangesloten is. Als batterij aangesloten, wordt batterij geladen met geconfigureerde constante stroom tot volledig geladen. Laden beëindigd door detectie van negatieve dV/dt gedurende 5 minuten. Na voltooiing schakelt lader over naar drijfladen met stroom C/40. Lader scheidt zich van batterij na verstreken van maximale drijfladtijd.

Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1

1 - dV/dt

2 - laadtijd in minuten

Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1
Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1

1 - Laadtijd

2 - Overgebrachte lading aan batterij

Aanvullende informatie over NiMH batterijen:

  1. Energizer NiMH Handleiding
  2. Battery University - NiMH Laden

Code

charger.ino (Hoofdbestand)

//THis voor laden 9V NiMH batterij
//Batterij kan 6, 7 of 8 1.25V cellen hebben
//Dit maakt nominale spanning tussen 7.5 en 10V
#include 
#include 
#include 
#include 
#include 
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // zet LCD adres op 0x27 voor 16 chars en 2 regels display
//Hardware definities
#define MCP4725_ADDR 0x60 //DAC adres
#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 //Lage zijde weerstand van spanningsdeler
#define R2 19910//Hoge zijde weerstand van spanningsdeler
#define ADC_REF_VOLTAGE 4.89 //Arduino echte voedingspanning of AREF spanning voor correcte ADC naar spanning vertaling
#define R3 3300.0
#define R4 6800.0
#define AMP_GAIN (1+R4/R3) //Versterking
//Verschillende definities
//#define MINIMUM_VOLTAGE 8.1 //Minimale spanning na regelaar
#define BATTERY_GOOD_VOLTAGE_THRESHOLD 7.0
#define MAXIMIM_ALLOWED_CURRENT 200 //Maximaal toegestane batterijstroom mA (harde afkapping)
#define MAXIMUM_BATTERY_VOLTAGE 15.0 //Maximaal toegestane batterijspanning V (harde afkapping)
#define VOLTAGE_STEP 0.001 //Stap voor spanningregeling
#define POINTS_FOR_AVERAGING 100 //Hoeveel punten voor gemiddelde
#define MEASURE2MEASURE_US 5000 //Tijd tussen metingen in microseconden (moet meer dan 200 zijn vanwege twee analoge reads)
#define VOLTAGE_LOG_TIME_MIN 30.0 //Tijd in minuten om spanning op te slaan voor dV/dT afkapping
#define MINUTES_TO_LOG 5
#define TRICKLE_CURRENT_RATIO 40 //Drijfstroom ratio
//Lader statussen
#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;
//Berichten
const char msg_battery_detected[] PROGMEM ="Batterij gedetecteerd";
const char msg_no_battery[] PROGMEM ="Geen batterij";
const char msg_battery_ok[] PROGMEM ="Batterij OK";
const char msg_voltage_too_low[] PROGMEM ="Batterijspanning te laag";
const char msg_voltage_too_low_short[] PROGMEM ="V Batterij laag";
const char msg_ramp_up[] PROGMEM ="Opbouwen";
const char msg_charging[] PROGMEM ="Laden";
const char msg_space[] PROGMEM =" ";
const char msg_ramp_down[] PROGMEM ="Afbouwen";
const char msg_trickle_charge[] PROGMEM ="Drijfladen";
const char msg_no_current[] PROGMEM ="Geen stroom";
const char msg_current_unreachable[] PROGMEM ="Stroom onbereikbaar";
const char msg_current_unreachable_long[] PROGMEM ="Kan gewenste stroom niet bereiken";
const char msg_completed[] PROGMEM ="Voltooid";
const char msg_charge[] PROGMEM ="Lading";
const char msg_high_current[] PROGMEM ="Hoge stroom";
const char msg_regulation_fault[] PROGMEM ="Regelingsfout";
const char msg_overcurrent[] PROGMEM ="Stroom te hoog";
const char msg_overvoltage[] PROGMEM ="Spanning te hoog";
const char msg_trickle_completed[] PROGMEM ="Drijfladen voltooid";
//*************************** Laadparameters *****************************************
//*****************************************************************************************
float target_current_mA=30; // Laadstroom mA
float battery_nominal_capacity_mA=170; // Nominale capaciteit van batterij mA
float max_time_for_trickle_charge=6; // Maximale drijfladtijd in minuten
//*****************************************************************************************
//*****************************************************************************************
struct mytime {
  unsigned char hours;
  unsigned char minutes;
  unsigned int total_minutes;
} elapsed_time;
void setup() {
  pinMode(CHARGE_RELAY_PIN, OUTPUT);
  disconnect_charging_circuit(); // Scheid lader van batterij
  Wire.begin(); // I2C
  dac_write_voltage(0); // Zorg dat laagst mogelijke stroom ingesteld
  Serial.begin(115200);
  last_second=second();
  lcd_last_second=second();
  log_last_second=second();
  Timer1.initialize(MEASURE2MEASURE_US); // Gebruik voor meten spanning en stroom batterij (microseconden)
  Timer1.attachInterrupt(read_hw); // koppelt read_hw() als timer overflow interrupt
  averaging_index=0;
  sec_index=0;
  min_index=0;
  charger_state=0; // Initiële staat van state machine
  wanted_dac_voltage=0; // Zorg dat minimum voltage output
  last_blf=1.0;
  trickle_current_mA=battery_nominal_capacity_mA/TRICKLE_CURRENT_RATIO;
  //ChargingTimeMillis=0;
  //LCD
  lcd.begin(16,2);
  lcd.backlight();
  lcd.clear();
  update_lcd(F("Opstarten..."),empty_string);
  delay(1000);
}
float log_battery_voltage(){
  // Log slechts een keer per seconde
  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 {
    // Als één minuut gelogd
    // Bereken gemiddelde per minuut
    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

main.ino

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_mAMAXIMUM_BATTERY_VOLTAGE ) charger_state=OVERVOLTAGE;
      if (abs(current_mA-target_current_mA)>0.2){
        if (current_mA0.01) wanted_dac_voltage=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_averagetrickle_current_mA) {
        if (wanted_dac_voltage>VOLTAGE_STEP) {
          wanted_dac_voltage=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_mA0.01) wanted_dac_voltage=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("Stroom onverwacht gevallen"));
      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;
      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)+String("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("Kan stroom niet verlagen naar doel"));
      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

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) 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

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:
      msg=v+String(F("V "))+i+String(F("mA"))+" "+msg_time;
      break;
    case TRICKLE:
      msg=v+String(F("V "))+i+String(F("mA"))+" "+msg_time;
      break;
    default:
      msg=v+String(F("V "))+i+String(F("mA"));
  }
  msg.replace("-","");
  return msg;
}
String construct_time_string(mytime timeinfo){
  String mystring=String(timeinfo.hours,DEC)+String(F(":"))+String(timeinfo.minutes,DEC);
  return mystring;
}

calculations.ino

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 char_message[len+1],myChar;
  for (k = 0; k < len; k++){
    mess+= (char)pgm_read_byte_near(signMessage + k);
  }
  return mess;
}

New LiquidCrystal bibliotheek gebruikt voor dit project

Geen preview beschikbaar (alleen download).

Schema's

Geavanceerde 9V NiMH Batterij Lader – Slim, Precies en Betrouwbaar – Versie 1

Fritzing bestand: charger_AgK96zxw2T.zip

Productieproces

  1. Slimme Plantenalarmer – DIY Sensor voor Optimale Hydratatie
  2. Bouw een Italiaanse Woordklok met Arduino Nano, NeoPixel, LCD en RTC
  3. Bespaar Arduino-pins: Sluit een 4x3-toetsenblok aan met slechts 3 verbindingen
  4. Bouw een Betrouwbare Sigfox kWh Meter met Arduino MKR Fox 1200
  5. Bluetooth Temperatuurmeter met Arduino Nano
  6. Contactloos Gebaarslot: Veilige toegang met APDS-9960 en Arduino
  7. Arduino Nano Companion Kit – Essentiële Componenten & Hulpmiddelen voor DIY Electronica
  8. USB MIDI Adapter Kit voor Arduino Micro / Leonardo – Complete Onderdelenlijst
  9. Geïsoleerde analoge ingangsschakeling voor Arduino bouwen
  10. RGB 32-band audio-spectrumvisualisator – Arduino LED-matrixproject
  11. Precisie Reactietijd Test met Arduino UNO