Showing posts with label Voltage divider. Show all posts
Showing posts with label Voltage divider. Show all posts

Thursday, August 11, 2016

ESP8266 - Internal ADC 2 - the easy way example



This is just a very quick example on how to add a Voltage Divider to the ESP8266 Internal ADC input to increase the ADC input range.

For more theory behind, please take a look at the previous related article: ESP8266 - Internal ADC


Today project:

      Measure a voltage input range from 0-5V with the ESP8266 internal ADC


What do we need:

  • 2 Resistors for the voltage divider, R1=105.6k, R2=24.08k. I am using here precision resistors and the values are measured values with a proper calibrated bench meter.
  • a good, trustable, calibrated ok Multimeter.
  • some wires to connect all together.


I will not insist on connections, take a deeper look at the previous article about


Voltage divider schematic and Vout (ADC input voltage) formula




How do we do it :


1. Measure your Max desired input voltage
    In my case, Max Input Voltage (Vmax) = 5.1919 (measured)



 
2. Measure Resistors values:
    R1 = 105.6k
    R2 = 24.08k


 
3. Check if Full Scale Value at the voltage divider output is inside the ADC defined domain:
  • Calculated : Vout = (R2/(R1+R2))*Vin = 0.964072733 V
  • Measured = 0.96038V
     Good enough for the precision we are looking for. ESP8266 ADC is 10bit only and not exactly the most accurate in town



4. Calculate Voltage Divider Ratio:
  • Vdivider Ratio= Max Input Voltage/Fullscale value =  5.405976676



5.  Read ADC value : 
       adcr = adc.read(0)
      print("    ReadADC     : "..adcr)
     Average result for adcr = 1017

THIS IS IMPORTANT !! It tell us that we are inside the ADC domain as adcr < 1024 !

IF adcr > 1023 then you need to adjust your voltage divider resistors to fit inside ADC domain!



6. Calculate LSB  in 2 ways to cross check that we have the right value:
  • LSB = Input Voltage read by multimeter/ADC readed Value = 
                   =  5.1919 / 1017 =  0.005105113 V
      OR
  • LSB = (ADC Input  pin read Voltage by multimeter/ADC readed Value/)*Vdivider Ratio = 
                  = (0.96038/1017)*5.405976676 = 0.005105113 V
       It looks that we have the right LSB Value for our exercise!

     If you want to know also the ADC LSB, then
       LSB = ADC Input  pin read Voltage by multimeter/ADC readed Value  =   
                =  0.96038/1017 = 0.000944346 V

    Guess what's happening if you multiply ADC LSB with Vdivider ratio :)




7. Software Implementation for the ADC read function:

 function readADC()
      ad = 0
      LSB = 0.005105113 --calibrate based on your voltage divider AND Vref!
      adcr = adc.read(0)
      ad= adcr*LSB       
      print("    ReadADC     : "..adcr)
      print("    Read Voltage     : "..ad)
      return ad
end

and some results in the terminal window:

> SENT: readADC()
readADC()
    ReadADC     : 1017
    Read Voltage     : 5.191899921
>






Saturday, June 6, 2015

Part 2 : ACS712 Current Sensor Board - Programming




   In this part we will explore the programming side of the ACS712 Current reading function. 

   If you didn't read yet the previous article about ACS712 Current sensor Board it might be a good idea to start from there as it will be easier to understant how it's working the entire process. You can find there schematics, theory of operation and technical related things. 


What we will need:



   ACS712  Software
 
  For programming CBDBv2 Board and uploading the drivers and the software we will continue to use the LuaUploader as before.




  1. Reading ACS712  with VLSAM DC

In this case, as VLSAM DC Output is already scaled to ADC input of 0-1V the only things that we need to do for proper measurements of the current are:
  • establish the LSB value for ADC 
  • extablish your own desired VLSAM Offset value. It's desired to not start your currrent range scale exactly at 0V ADC (ADC linearity, etc), for easing the calculations, just set Vdiv to have an output  of about 100mV for ZERO Amps. As ASC712/20 gives us 100mV/A that means that we will have 100mA reading for 200mV and so on, just substract your offset.   

         offset = 0.1130             -- CALIBRATE !! 
          LSB = 0.000976563   -- CALIBRATE !!
 

        function readADC_VLSAM()
            ad = 0
            ad=adc.read(0) --calibrate based on your voltage divider AND Vref!
            print("\nReadADC     : " ..ad)
           ACS712 = ad*LSB
           print("Current Read: " ..string.format("%g",ACS712-offset) .."mA")
           return ACS712
        end


   For increasing the precison of the measurement just take a bigger number of readings and obtain a average value. 128 steps looks quite ok, but in case of more noise in the system just use a bigger number and eliminate the most extreme values for the obtained interval.


function readADC_avg_VLSAM()
      ad1 = 0
      i=0
      while (i<128) do
         ad1=ad1+adc.read(0) --calibrate based on your voltage divider AND Vref!
         tmr.delay(1000)
         --print(ad1)
         i=i+1
      end
      ad1 = ad1/128
      ACS712 = ad1*LSB
      print("Current Read: " ..string.format("%g",ACS712-offset) .."mA")
      return ACS712avg
end





 2. Direct Reading ACS712 Current Sensor

    Another available option is to read directly the ACS Current sensor output.  

    In this case because the sensor output will swing between 0 and 5V, with ZERO Amps reading at 2.5V, we will use the CBD V2 EVO Board voltage divider or add your own in case of using a different board without such capabilities.

   To better understand how it will work in this case, please take a look at the ESP8266 Internal ADC article where was also discussed the voltage divider for ADC input.

      -- Select  ADC SOURCE
     ADC_SRC = 5  -- select Voltage Divider / Current Input by default !
     gpio.mode(ADC_SRC,gpio.OUTPUT, gpio.PULLUP)

     gpio.write(ADC_SRC,1)  -- Voltage Measurement - Voltage Divider Source selected

     dival = 0.0009605      -- ADC volt/div value - CALIBRATE !!  -- USB
     resdiv = 4.31447        -- Voltage divider Ration - CALIBRATE!!
     offset = 2.45               --ACS712 measured offset - CALIBRATE !!



     function readADC_VDIV()
          adcV = 0
          advr = 0
          advr=adc.read(0)
          print("\nADCV Step   : " ..string.format("%g",advr).." steps")
         adcV=advr*dival*resdiv

         print("Current       : " ..string.format("%g",adcV-offset).." mA")
         return adcV
    end

  
    For increasing the precison of the measurement just take a bigger number of readings and obtain a average value as before, but this type you need more, like 512 or even 1024. Also some extra filtering on ACS712 output will be great.

    Despite the fact that might be easier to use the Voltage divider, because of the high noise from the ASC712 output I will recommend you to use option1 - VLSAM DC . It will give you more accurate values than option2 with Voltage divider.




  

Tuesday, June 2, 2015

Mailbag Arrival !! ACS712 Current Sensor Board




   Because of the very cheap ACS712 Current Sensor Board modules available all over the place, I really think that everybody heard about ACS712 sensor from Allegro MicroSystems.

  As I was asked if "Can be used as a current sensor module for our ESP8266 Projects without too much hustle?", I ordered few of them and let's explore them for the answer :)

   The ACS712 device consists of a precise, low-offset, linear Hall circuit with a copper conduction path located near the surface of the die. That means that it is a isolated path device, a very nice thing if you want to monitor MAINS Current or any other High Voltage path that you want isolated from your device.

IMPORTANT NOTE !!  This device is a Hall Effect transducer.  It should not be used near significant magnetic fields.  If you have a magnetic noisy environment take a look at other solutions like isolation transformers, isolation OpAmps, linear isolation circuit, bus path isolation, etc.


 Theory of operation 

 Applied current flowing through the ACS712 copper conduction path generates a magnetic field which the Hall IC converts into a proportional voltage. Device accuracy is optimized through the close proximity of the magnetic signal to the Hall transducer.
A precise, proportional voltage is provided by the low-offset, chopper-stabilized BiCMOS Hall IC, which is programmed for accuracy after packaging.

  The ACS712 outputs an analog signal, VIOUT that varies linearly with the uni- or bi-directional AC or DC primary sampled current, IP (between IP+ e IP-) within the range specified.

  It requires a power supply of 5V (VCC) and two capacitors to filter power supply and output.
CF is recommended for noise management, with values that depend on the application.

Typical application from the Datasheet: 



   ACS712 Current Board comes in 3 different models of this integrated circuit, depending on the maximum measured current : 5A  20  or  30A

5A Module20A Module30A Module
Supply Voltage (VCC)     5VDC     5VDC        5VDC
Measurement Range   -5 to +5 A   -20 to +20 A        -30 to +30 A
Voltage at 0A   VCC/2     VCC/2      VCC/2
Scale Factor   185 mV/A   100 mV/A      66 mV/A
Variant  ACS712ELC-05A   ACS712ELC-10A     ACS712ELC-30A


   For our today experiment, I will use the one below, a 100mV/A one:




What we will need:
  •  ACS712 Current Board module (pick your desired choice -  5A, 20A, 30A )
  • CBDB Board ( or any other ESP8266 Board you may like but who has the same capabilities)
  •  USB adapter (take a look on Part 1 for details about the USB Adapter 
  •  2 x 10 Ω /10 W  - Load resistors
  • Connection wires - various colors
  • Breadboard
  • Bench Power Supply 
  • Voltage Level Shifter and dc amplifier module - VLSAM DC(see details below)


Voltage Level Shifter and amplifier module - VLSAM DC

  As our main goal is to explore the posibility to use the ACS712 module with ESP8266 devices, from the description above and datasheet we can identify very quick some problems that need to be solved to make ACS712 a "ESP Friendly" device:

ASC712: 
  •  5 Vdc power supply device
  •  measure positive and negative 5/20/30Amps, corresponding to the analog output 100mV/A BUT
  • "ZERO" point, means no test current through the output voltage is VCC / 2 =5v/2=2.5V !

ESP8266:
  • 3V only device
  • ADC range  max: 0-1V

  What we will need is to "move" the "ZERO" point as low as we want to GND and in the same time to amplify the received analog signal to obtain a better resolution in the desired measuring range. (don't forget, for our experiment we have DC only voltage here and just one current flow direction)

   So, what can be done? 

Probably they are some other solutions to this problem, and if you know any of them, please feel free to share in the comments below, but the clasical one is to use a difference amplifier:





    This kind of amplifier uses both inverting and non-inverting inputs of a OPAmp with a gain of one to produce an output equal to the difference between the inputs. Actually if you take a closer look it is a special case of the differential amplifier.
   The good thing is that in the same time you can also choose the resistances values in a way to amplify the difference. 

    If all the resistor values are equal, this amplifier will have a differential voltage gain of 1. The analysis of this circuit is essentially the same as that of an inverting amplifier, except that the noninverting input (+) of the op-amp is at a voltage equal to a fraction of V2, rather than being connected directly to ground. As would stand to reason, V2 functions as the noninverting input and V1 functions as the inverting input of the final amplifier circuit. Therefore:

                  Vout = V2 - V1

    If we want to provide a differential gain of anything other than 1, we would have to adjust the resistances in both upper and lower voltage dividers, necessitating multiple resistor changes and balancing between the two dividers for symmetrical operation.

    A limitation of this simple amplifier design is the fact that its input impedances are rather low compared to that of some other op-amp configurations, most notably the noninverting (single-ended input) amplifier. Each input voltage source has to drive current through a resistance, which constitutes far less impedance than the bare input of an op-amp alone. It is a solution to this problem, fortunately, quite simple,  all we need to do is “buffer” each input voltage signal through a voltage follower.


   For our case, as we want to "substract" the ACS712 - 2.5Vdc to move the "ZERO" point near GND, we will have:
  • V1 = 2.5V - can be easy obtained from 5V with a buffered voltage divider
  • V2 = VIOUT - output voltage from ASC712 Module
  • Amplification:
    • IF R1=R2=R3=R4=10K 
      •  output for ADC input of 100mV/A
      • max range: 10A
      • resolution:  10mA
    • Changing range  R1=R2=10K, R3=R4=100k 
      • output for ADC input of 1V/A
      • max range: 1A
      • resolution: 1mA


VLSAM DC Schematic


And the simulation result for a 0-10A, 2.5-3.5 VIOut sweep:


  • GREEN      - Voltage divider output  : 2.5Vdc
  • FUCHSIA  -  ASC712 VIOut            : 2.5 -> 3.5Vdc
  • RED           -  ADC input                   : 0 -> 1Vdc


So far so good, let's move it on a breadboard for a quick test:




Monday, May 18, 2015

P2 - Battery Monitor System - Web Server and data upload



 ================== W A R N I N G ! ! =================
Li-Ion batteries are very dangerous if not handled properly.
Use them only with proper protection circuits and chargers.
DO NOT OVER CHARGE / BURN / OPEN Li-Ion Bats
     Using any of the informations available on esp8266-projects.com
 is on your own risk !!
===============================================
 
    For any new CBDBv2 Evo orders/requests please feel free to use as usual:
     tech at esp8266-projects.com.
     If you want for your own experiments to order CBD v2 EVO bare PCBs only, you can also do it directly at Dirty PCBs, our preferred PCB House:
  http://dirtypcbs.com/view.php?share=5876&accesskey=3d0fd70d53d6dc20c0bf0255f67cde65
------------------------------------------------------------------------------------------------------------------



As promised, today we will continue with our Live Battery Monitor System.


Subjects of the day:
  •  WebServer interface for direct access
  •  Live Data upload over the Internet for wordwide access on Thingspeak.com.


What we will need:  

For schematics, theory of operation and technical related things please see previous article: Battery LIve Monitor System




BLMS Software
 
  For programming CBDBv2 Board and uploading the drivers and the software we will continue to use the LuaUploader as before.




1. Define used GPIO pins and variables:

ADC_SRC = 5                    -- GPIO14 - select Voltage Divider / Current Inputsda=2                                   -- GPIO4  - SDA
scl=1                                    -- GPIO5  - SCL
gpio.mode(ADC_SRC,gpio.OUTPUT, gpio.PULLUP)
gpio.write(ADC_SRC,1)      -- Voltage Measurement - Voltage Divider Source selected
gpio.write(ADC_SRC,0)      -- Current Measurement - Current Shunt Monitor output selected
voltdiv= 0.00412                -- Voltage reading calibration
dival = 0.00096                  -- ADC volt/div value - CALIBRATE !! 
resdiv = 4.31447                -- Voltage Divider Ratio - CALIBRATE!!
divalI = 0.9425                  -- Current volt/div ratio - CALIBRATE!!
cpct = 0                              -- Calculated Delivered Energy
adcI = 0                             -- ADC readings -  Curent

adcV = 0                            -- ADC readings - Voltage
pwr = 0                              -- Calculate Power
t=0                                     -- time  





2. READ ADC Process function  and calculations 

function readUI()

   gpio.write(ADC_SRC,1)
      advr=adc.read(0)
      adcV=advr*dival*resdiv
      print("\nVoltage     : " ..string.format("%g",adcV).." V")


   tmr.delay(10000)
   gpio.write(ADC_SRC,0)
      adcr=adc.read(0)
      adcI=adcr*divalI
      print("Current     : " ..string.format("%g",adcI).." mA")
   --instantaneous Power consumption calculation
   pwr = adcI*adcV
   print("Power       : " ..string.format("%g",pwr).." mW")

   -- total delivered Energy calculation
    if (adcV > 1) then
      cpct=cpct+pwr*0.002778/1000
      t = t +1
       print("Energy      : " ..string.format("%g",cpct).." W")
  else  tmr.stop(0)
        dtime = t/360         -- calculate discharge time

        print("Discharge Time: %.2fh  ",dtime)
  end
end



3. BMS WebServer

srv=net.createServer(net.TCP)
  srv:listen(80,
     function(conn)
        conn:on("receive",function(conn,payload)
        --print(payload)
        conn:send("HTTP/1.1 200 OK\n\n")
        conn:send("<META HTTP-EQUIV=\"REFRESH\" CONTENT=\"5\">")
        conn:send("<html><title>Battery Live Monitor System - ESP8266</title><body>")
        conn:send("<h1>Battery Live Monitor System - ESP8266</h1><BR>")
        conn:send("Voltage :<B><font color=red size=4>" 

                          ..string.format("%g",adcV).." V</font></b><br>")
        conn:send("Current :<B><font color=blue size=4>"

                         ..string.format("%g",adcI).." mA</font></b><br>")
        conn:send("Power   :<B><font color=red size=4>"

                        ..string.format("%g",pwr).." mW</font></b><br>")
        conn:send("Energy  :<B><font color=green size=4>"

                       ..string.format("%g",cpct).." Wh</font></b><br>")
        conn:send("<BR><BR><BR>Node.HEAP     : <b>" .. node.heap() .. "</b><BR>")
        conn:send("IP ADDR    :<b>".. wifi.sta.getip() .. "</b><BR>")
        conn:send("TMR.NOW    :<b>" .. tmr.now() .. "</b><BR<BR><BR>")
        conn:send("</html></body>")
        conn:on("sent",function(conn) conn:close() end)
        conn = nil    
      end)
end)
 



4. Thingspeak.com data upload  

 function sendDataTh()
    print("Sending data to thingspeak.com")
    conn=net.createConnection(net.TCP, 0)
        conn:on("receive", function(conn, payload) print(payload) end)
        conn:connect(80,'184.106.153.149')
        conn:send("GET /update?key=YOURKEYHERE&field1="..adcV.."&field2="..adcI.."&

                           field3="..pwr.."&field4="..cpct.."HTTP/1.1\r\n")
        conn:send("Host: api.thingspeak.com\r\n")
        conn:send("Accept: */*\r\n")
        conn:send("User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)\r\n")
        conn:send("\r\n")
        conn:on("sent",function(conn) print("Closing connection")
        conn:close()
    end)
    conn:on("disconnection", function(conn) print("Got disconnection...")
    end)
end





5. MAIN program


If you have a new ESP module or is the first time when using it on a new WIFI network, don't forget to do the WIFI setup:

        -- One time ESP Setup --
        wifi.setmode(wifi.STATION)
         wifi.sta.config ( "YOUR_WIFI_SSID" , "PASSWORD" ) 
         print(wifi.sta.getip())



-- Read ADC data every 10s, print values on local Web interface and send them to thingspeak.com
 tmr.alarm(0, 10000, 1, function() readUI() sendDataTh() end)




   For testing, just save the code on ESP as 'blmsweb.lua', restart ESP and run:   
  
            dofile("blmsweb.lua")      -- Start the Battery Live Monitoring System

            print(wifi.sta.getip())       --print your BMS WebServer IP address





BLMS - First RUN

  If you want the BLMS software to start automatically when your CBDBv2 Evo Board starts or reboots, then you neet to create and add some lines in your 'init.lua' file:
  
            dofile("blmsweb.lua")      -- Start the Battery Live Monitoring System

Save the code on ESP as 'init.lua', restart ESP. It should reboot and restart automatically the program.




Open your favorite Web browser and type your new BLMS Web Server IP address. If all ok, it should look like below :

 
BLMS - Web Server interface - 20ohm Load




Thingspeak data upload

I will not insist on the Thingspeak.com account setup, it's a pretty simple process:
  1. Sign Up for a ThingSpeak account
  2. Create a new Channel by going to your Channels page and clicking Create New Channel
  3. Update your Channel via URL:
    https://api.thingspeak.com/update?api_key=YOUR_CHANNEL_API_KEY&field1=7
  4. View your Channel feed:
    https://api.thingspeak.com/channels/YOUR_CHANNEL_ID/feeds.json
Take a look at this Tutorial for more details for common devices and applications



Thingspeak.com live data uploaded - 10ohm/10W Load for stress test calibration




You can also see on graph below the moment when battery protection kicks in, around 2.4V, 2.37745 V in this case, to be more exactly:




In the video below live monitoring data, with 10 and 20ohm resistor Load:






Thursday, May 14, 2015

Battery Live Monitor System - ESP8266 CBDBv2


 ================== W A R N I N G ! ! =================
Li-Ion batteries are very dangerous if not handled properly.
Use them only with proper protection circuits and chargers.
DO NOT OVER CHARGE / BURN / OPEN Li-Ion Bats
     Using any of the informations available on esp8266-projects.com
 is on your own risk !!
===============================================


     For any new CBDBv2 Evo orders/requests please feel free to use as usual:
     tech at esp8266-projects.com.
     If you want for your own experiments to order CBD v2 EVO bare PCBs only, you can also do it directly at Dirty PCBs, our preferred PCB House:

  http://dirtypcbs.com/view.php?share=5876&accesskey=3d0fd70d53d6dc20c0bf0255f67cde65


------------------------------------------------------------------------------------------------------------------


      One of the main problem in battery powered projects is to choose/use the proper battery size/model/type. As market is flooded now with a lot of low quality batteries claiming thousands of mAh ( Ultrafire fakes stories is just an example) the only way to proper check them is to run a set of tests.
   
    A simple basic capacity test / loading / discharging time values can give you valuable informations about the parameters and health of the battery. And of course also you can see how are looking the numbers against the datasheet claims :).


  Few general considerations that I want to bring to your attention:
  • The capacity of a battery tells you how much charge it can store.  This means that they can theoretically provide a current of 2A for one hour, 1A for two hours, or 200mA for ten hours. In reality a 2,000mAh battery would not provide 20mA for 100 hours, nor 2A for one hour. 
  • Discharge load -  this is a tricky one and most of the time not clear explained by battery manufacturers in their datasheets. Mostly of them are using a discharge load of 0.1C, that means one-tenth of current capacity when rating their batteries. Therefore, a battery which can provide 200mA for 10 hours would be rated at 10x200 = 2,000mAh !!   
         A faster discharge rate would result in less energy being extracted from the battery, and
         therefore the measured capacity would be lower. If you are using batteries in energy-hungry
         devices that need more than 0.1C, the real capacity will be lower than that published.




     A "good-to-have" add-in for a battery powered project is a system that will be able to monitor over the entire battery lifetime at least few parameters like, voltage, current, power consumption and stored energy between charges. 


   What we will need:  


   Schematics:

  As the CBDBv2 Evo DevBoard has already Voltage and Current measurements capabilities onboard, the only thing that we will need to do is to connect the battery module that we want to measure to the board:



           GREEN  - V+
           BLUE    -  GND
           RED      -  SH_IN+
           WHITE -  SH_IN-



    By default the CBDBv2 Evo Board comes prepared for Li-Ion batteries voltage range, with the onboard default voltage divider values and Rshunt you have VmaxIN=4.3V and ImaxIN = 1A. If you need a different Voltage/Current range just change/ask for different default values.


CBDB v2 Evolution DevBoard with INA21x Current monitor


    The Current shunt monitor used for this project is a INA21x family one. 

    The INA210, INA211, INA212, INA213, INA214, and  INA215 are voltage-output, current-shunt monitors that can sense drops across shunts at common-mode voltages from –0.3 V to 26 V, independent of the supply voltage. 

   
    Five fixed gains are available: 50 V/V, 75 V/V, 100 V/V, 200 V/V, 500 V/V, or 1000 V/V. 
    The low offset of the zero-drift architecture enables current sensing with maximum drops across the shunt as low as 10-mV full-scale.
 

   These devices operate from a single 2.7-V to 26-V power supply, drawing a maximum of 100 μA of supply current. All versions are specified over the extended operating temperature range (–40°C to 125°C), and offered in an SC70 package. The INA210, INA213, and INA214 are also offered in a thin UQFN package.


 INA21x Features:

• Wide Common-Mode Range: –0.3 V to 26 V
• Offset Voltage: ±35 μV (Max, INA210) (Enables Shunt Drops of 10-mV Full-Scale)
• Accuracy:
– ±1% Gain Error (Max over Temperature)
– 0.5-μV/°C Offset Drift (Max)
– 10-ppm/°C Gain Drift (Max)
• Choice of Gains:
– INA210: 200 V/V
– INA211: 500 V/V
– INA212: 1000 V/V
– INA213: 50 V/V
– INA214: 100 V/V
• Quiescent Current: 100 μA (max)



     For any new CBDBv2 Evo orders/requests please feel free to use as usual: tech at esp8266-projects.com.
     If you want for your own experiments to order CBDBv2 EVO bare PCBs only, you can also do it directly at the PCB House:  

  http://dirtypcbs.com/view.php?share=5876&accesskey=3d0fd70d53d6dc20c0bf0255f67cde65



    For that ones of you that want to use a different Board or just want to add these functions to a ESP8266 Module, you just need:
  • for Voltage -  add and size on your needs the voltage divider as explained in details in the previous article about ESP8266 Internal ADC. 
  • for Current - add your favourite Current Monitor but don't forget about the VmaxIN for ESP8266 ADC of about 1V!




   BLMS Software
 
  For programming CBDBv2 Board and uploading the drivers and the software we will continue to use the LuaUploader as before.


   How to Measure Battery Capacity

   When measuring the real battery capacity what we are interested in is the amount of energy stored in a battery since it is this energy we need to power our devices. Stored energy is measured in Watt-hours - the same unit used to measure our domestic electricity consumption (where 1,000Wh = 1kWh = 1 unit of electricity).

   In order to measure the stored energy in a battery a power resistor is used as the load, and a fully charged battery is fully discharged through it. By measuring the voltage across this resistor at regular intervals during the discharge process it is simple to calcuate the total energy dissipated and therefore the total energy which had been stored in the battery.

  Using Ohm's Law (I=U/R) we can then calculate the current flowing through the load since we know the voltage across it. Instantaneous power is given by multiplying the measured voltage by the calculated current (P=U*I).

   By taking readings for a certain amount of time until the battery is completely discharged, and adding up the energy dissipated in each time interval, we can calculate the total energy taken from the battery and dissipated in the resistor and therefore the total energy that was stored in the fully charged battery.



Software Implementation
 
1. Define used GPIO pins and variables:

ADC_SRC = 5                    -- GPIO14 - select Voltage Divider / Current Inputsda=2                                   -- GPIO4  - SDA
scl=1                                    -- GPIO5  - SCL

gpio.mode(ADC_SRC,gpio.OUTPUT, gpio.PULLUP)

gpio.write(ADC_SRC,1)      -- Voltage Measurement - Voltage Divider Source selected
gpio.write(ADC_SRC,0)      -- Current Measurement - Current Shunt Monitor output selected
 

voltdiv= 0.00412                -- Voltage reading calibration
dival = 0.00096                  -- ADC volt/div value - CALIBRATE !! 
resdiv = 4.31447                -- Voltage Divider Ratio - CALIBRATE!!
divalI = 0.9425                  -- Current volt/div ratio - CALIBRATE!!
cpct = 0                              -- Calculated Delivered Energy
adcI = 0                             
-- ADC readings -  Curent

adcV = 0                            -- ADC readings - Voltage
pwr = 0                              -- Calculate Power
t=0                                     -- time 
 


2. READ ADC - Voltage  

function readADC_Voltage()
      adcV = 0
      advr = 0
      advr=adc.read(0)
      print("\nADCV Step   : " ..string.format("%g",advr).." steps")
      adcV=advr*dival*resdiv
      print("Voltage     : " ..string.format("%g",adcV).." V")
      return adcV
end


 

2. READ ADC - Current
    
function readADC_Current()
      adcI = 0
      adcr = 0
      adcr=adc.read(0)
      adcI=adcr*divalI
      print("ADCI Step   : " ..string.format("%g",adcr).." steps")
      print("Current     : " ..string.format("%g",adcI).." mA")
      return adcI
end


 

2. READ ADC Process function  and instantaneous Power consumtion calculation 

function readUI()
   gpio.write(ADC_SRC,1)         --select source
   adcV = readADC_Voltage()
   tmr.delay(10000)
   gpio.write(ADC_SRC,0)         
--select source
   adcI = readADC_Current()
   pwr = adcI*adcV
   print("Power       : " ..string.format("%g",pwr).." mW")
end

 


3. Number format function for proper LCD printing
  
nr_format = function (fnr,unit)
   if (fnr > 1000) then fnri=fnr/1000
                        uniti=string.sub(unit, 2)  
                        nrf=string.format(" %.3f%s ",fnri, uniti)
   else
   if (fnr < 100) then
                    if (fnr < 10) then
                                   nrf = string.format("  %.1f%s ",fnr, unit)
                      else nrf = string.format(" %.1f%s ",fnr,unit)
                    end
    else nrf = string.format("%.1f%s ",fnr,unit)
   end
  end   
   return nrf
end




4. LCD Print - > Voltage / Current / Energy / Power

    For more details about the I2C LCD Driver , please take a look at the ST7032i LCD Driver Article
 

LCDout = function()
   if (t==0) then st7032i:lcd_clear() end
   if (adcV > 1) then
      st7032i:lcd_print(1,1,string.format("%.3fV",adcV))
      adci = nr_format(adcI,"mA")
      st7032i:lcd_print(8,1,adci) 
      pwrp = nr_format(pwr,"mW")
      st7032i:lcd_print(0,2,pwrp)
      cpct=cpct+pwr*0.002778 --every 10 sec readings
      cpctp = nr_format(cpct,"mWh")
      t = t +1
      st7032i:lcd_print(8,2,cpctp)
  else  tmr.stop(0)
        dtime = t/360
        st7032i:lcd_print(0,1,string.format("Disc.Time:%.2fh ",dtime))
        st7032i:lcd_print(0,2,"Energy: ")
  end
end  


 

5. MAIN program

require('st7032i')
st7032i:init_i2c(sda,scl)
st7032i:init_LCD()
st7032i:lcd_clear()
st7032i:lcd_print(1,1,string.format("Battery Monitor"))
st7032i:lcd_print(3,2,string.format("Starting ..."))
 

tmr.alarm(0, 10000, 1, function() readUI() LCDout() tmr.delay(1000) end)




   For testing, just save the code on ESP as 'blms.lua', restart ESP and run:   
  

            dofile("blms.lua")      -- Start the Battery Live Monitoring System


Running BLMS program






    If you want the BLMS software to start automatically when your CBDB module starts or reboots, then you neet to create and add some lines in your 'init.lua' file:
  
            dofile("blms.lua")      -- Start the Battery Live Monitoring System

Save the code on ESP as 'init.lua', restart ESP. It should reboot and restart automatically the program.







That's all for today, next time we will continue with Web Interface and online logging system for the BLMS!