Poundland in the UK sell batteries branded by Kodak, but the cheapest ones are zinc chloride. I thought ZnCl has gone out of use decades ago, but it is surprising how often people are penny-wise and pound foolish. Particularly in pound shops 😉
So I am looking to make something to track the discharge of a battery. This is also useful to qualify NiMH and NiCd batteries – these fade with time and it is good to know what sort of capacity is left.
Every minute it reports the voltage and current from the batteries running through a 2.5V torch bulb, the third bulb is maintained at 2.5V to provide a reference. It transmits the signal using radio to a datalogger. I got a camera to take a picture every 15 minutes, as a video the results are reasonably clear. A picture tells a thousand words – the Zinc Chloride batteries are crap
The left-hand bulb is powered by the ‘cheap’ battery that Poundland sell for 9p, the middle is powered by the ‘dearer’ alkalines they sell at about 17p.
I have a Ciseco OpenKontrol Gateway as a datalogger. It stores data to a SD card, but there is no law saying you couldn’t send it to some kind of IoT host like Xively. So all I need is the interface to the batteries to track the voltage and current; I used a 2.5V torch bulb for the load because it does not seem unreasonable to me to expect batteries to be used in a torch, I use two batteries in series, which seems to be the most common configuration in a torch. On the way to ground I use a 1 ohm resistor so I can measure between ground and the top of the bulb the battery voltage and between the resistor and ground the voltage corresponding to the current. Using 1 ohm makes the calculation easy, and also drops about 0.3V so the bulb is run at 2.7V at the start.
Now if you are looking to do this in a big way then these guys at Battery Showdown are doing this is a more rigorous way but their hardware is dearer. I’m also not absolutely sure that a constant current discharge is typical of many loads. something like a digital camera with a switch mode PSU actually increases its peak load with decreasing battery voltage because it is more a constant power device and needs to draw more current at a lower voltage to run the focusing bits. Something with a linear voltage regulator is a constant current to the dropout voltage of the regulator, but you don’t often see linear regulators in battery-powered kit.
The bulb isn’t ideal either, with a varying resistance with temperature, so I’d probably favour using resistive loads, and getting four/five battery channels instead of two. But the visuals were great for the video.
If you have a PC that can take RS232 then simply wire the output of the PIC to the PC serial port via a Max232 chip or single transistor inverter if you don’t need the radio connection. Since I have the datalogger logging other sensors it seems easy enough to use it as is.
I used a PIC16F870 that has five A/D channels – this gives me two battery channels. Each battery channel has one A/D port connected to A in the diagram, another one to B and 0V is connected to C. The voltage at B is the same as the current since R is 1Ω and the voltage at A is the battery voltage itself. The 10-bit A/D converter goes up to 1024, by setting the reference voltage to be the PIC power supply at the high end and PIC ground at the low end I made the maths easier by setting the PIC supply voltage to 5.12 V (this is within the PIC operational voltage spec of 4 to 5.5V) in which case a straight divide by two gives V in * 100, and matches the requirements acceptably.
Doing this for the Poundland batteries shows the difference between the alkaline and the ZnCl batteries
If we take the service life as down to 2V from 3V initially, then you get 1.7 hours from the cheap ones and 5.8 hours from the alkalines.Alkaline cost/hr is 17*2/5.8=5.9p/hr and the cheap batteries cost 9*2/1.7=10.6p/hr.
As a result you pay about twice as much to run something using the cheap Poundland batteries, and you get to replace the batteries three times as often. Saving money therefore costs you a fortune in aggravation and generates more waste. You also have this sort of problem if you don’t pay attention and leave the used batteries in too long.
The device offers an insight into the capacity of rechargeable batteries which is the more useful feature of it.
I am not really sure I understand the pathology behind the second wind the NiMH cells seem to have. I’d discard it as a fault in reading except that – the current shows the bulb did not blow or become loose, and the A/D and voltage reference is multiplexed, so the error should have shown elsewhere. will repeat this and see if it happens again – probably not as this battery has been totally flattened so it is not the same battery.
For rechargeables I will modify this rig to take four individual cells and discharge through four resistors. That way I don’t back-charge any weak cells, and I don’t need to measure the current as the terminal voltage and Ohms Law will tell me the current. If I run 2.7Ω I will run about 440mA.
Schematic
JAL code
The JAL code for the PIC is 1311_datalogger_v06.jal
-- JAL 2.4i -- derived from univ-sensor_v06 -- Datalogger -- adjust power supply to 5.12V using the LM317 regulator. -- internal Vref = VCC = 5.12V -- Can just divide ADC by 2 and shift dec point by two places -- v06 12 Nov 2013 RM -- CC BY-SA 3.0 -- http://creativecommons.org/licenses/by-sa/3.0/ -- serial port designed for use with a Ciseco XBBO board and radio ( Google it) -- can also use w/o radio via a Max232 or a FTDI adapter instead -- radio is powered down while ADC measurement made to avoid loading regulator voltage include 16f870 pragma target clock 4_000_000 pragma target osc HS ; use a 4MHz crystal or resonator pragma target WDT ENABLED pragma target PWRTE ENABLED pragma target BROWNOUT ENABLED pragma target LVP DISABLED pragma target CP DISABLED pragma target CPD DISABLED -- HS, -BOD, ,-LVP, -WDT, -CP = 0x3F22 ;pragma target fuses 0x2007 include pic_general include print include format -- set all interrupt sources disabled ;INTCON = 0 -- ADC includes delays so don't include this myself -- V2 delay's -- include delay_any_mc -- more delay functions -- include extradelay -- the analogue pins pin_a0_direction = input pin_a1_direction = input pin_a2_direction = input pin_a3_direction = input -- there is no a4 analogue channel pin_a5_direction = input pin_c7_direction = input pin_c5_direction = output pin_c6_direction = output pin_A4_direction = output ; this is open drain so 3V3 pullup comes from XRF alias xrf_sleep is pin_A4 ; pull this low to start the XRF, let go and float up to enter sleep xrf_sleep=low ; must enable with ATSM2 -- now configure ADC (this is a 10-bit ADC) const bit ADC_HIGH_RESOLUTION = true const byte ADC_NCHANNEL = 1 const byte ADC_NVREF = ADC_NO_EXT_VREF include adc adc_init() -- ok, now setup serial, we'll use this -- to get ADC measures -- all this is because the 16f870.inc file is not right for serial hardware 8/5/13 alias RCIF IS PIR1_RCIF alias TXIF IS PIR1_TXIF alias BRGH IS TXSTA_BRGH alias RCIE IS PIE1_RCIE alias TXIE IS PIE1_TXIE alias TXEN IS TXSTA_TXEN alias TRMT IS TXSTA_TRMT alias SPEN IS RCSTA_SPEN alias OERR IS RCSTA_OERR alias CREN IS RCSTA_CREN -- end of -- all this is because the 16f870.inc file is not right for serial hardware alias PEIE IS INTCON_PEIE alias TMR1IE IS PIE1_TMR1IE alias CCP1IF IS PIR1_CCP1IF OPTION_REG = 0b00001111 ; PS asllocated to WDT and slowest rate selected (20ms*128) PEIE = high TMR1IE = high T1CON=0b00110001 ; set timer 1 running internal osc 8xprescaled CCP1CON=0b00001011 ; reset tmr1 on match, kicks off an A/D conversion ; these next values seem to call a period of 10s from previous project CCPR1H=244 ; decimal CCPR1L=36 ; decimal -- so count is 244*256+36 = 62500 At 4MHz each tick is 1uS, prescsaled by 8 to 8uS ticks. -- const byte cyclesmax=60*2 -- how many times to count 0.5s ticks var word mincounter = 0 ;var byte lastTMR0=0 ;TMR0=0 -- clear at start -- ok, now setup serial; const serial_hw_baudrate = 9600 const bit usart_hw_serial = TRUE const bit lowspeed=true include print include serial_hardware serial_hw_init() const byte booted[] = "aVVSTARTED--" const byte prefix[] = "aVV" const byte line1[] = "+++" const byte line2[] = "ATSM2r" const byte line3[] = "ATACr" const byte line4[] = "ATDNr" const byte line5[] = "ATID0AFFr" const byte vpfx[]= "VLT" procedure WaitOnOk() IS -- state machine - aim is to hang and -- if this doesn't get to 2 the WDT will start things over after 2 sec -- if it gets to 2 it clrwdt anbd returns var byte state=0 asm clrwdt while state !=2 loop if serial_hw_data_available then if state==0 & serial_hw_data == "O" then state=1 ; this is the only way to get to state 1 end if if (state == 1) & (serial_hw_data == "K") then state=2 else state=0 ; because O was followed by something that wasn't K end if end if asm clrwdt end loop end procedure procedure justify(word IN myval) IS -- value is going to be 0..1024 (in reality 0..512) -- hundreds are integers, ie 2 d.p. -- assumes radio is up and running format_word_dec(serial_hw_data,myval,3,2) -- 0 .23 serial_hw_data="-" end procedure function get_volts() return bit is var word reading1 var word reading2 var word reading3 var word reading4 var word reading5 asm clrwdt -- get ADC result, high resolution reading1 = adc_read_high_res(0)/2 asm clrwdt -- get ADC result, high resolution reading2 = adc_read_high_res(1)/2 asm clrwdt -- get ADC result, high resolution reading3 = adc_read_high_res(2)/2 asm clrwdt -- get ADC result, high resolution reading4 = adc_read_high_res(3)/2 asm clrwdt -- get ADC result, high resolution reading5 = adc_read_high_res(4)/2 asm clrwdt -- send it back through serial xrf_sleep=low -- start the radio a little before the data will be sent. -- may want to change the LLAP prefix and comma del if not using radio delay_100ms(2) asm clrwdt print_string(serial_hw_data,prefix) print_string(serial_hw_data,vpfx) serial_hw_data="A" justify(reading1) asm clrwdt delay_100ms(5) -- no need for this dela if going straight out on serial print_string(serial_hw_data,prefix) print_string(serial_hw_data,vpfx) serial_hw_data="B" justify(reading2) asm clrwdt delay_100ms(5) -- allows the LLAP data to be received properly asm clrwdt print_string(serial_hw_data,prefix) print_string(serial_hw_data,vpfx) serial_hw_data="C" justify(reading3) asm clrwdt print_string(serial_hw_data,prefix) print_string(serial_hw_data,vpfx) serial_hw_data="D" justify(reading4) asm clrwdt delay_100ms(5) asm clrwdt print_string(serial_hw_data,prefix) print_string(serial_hw_data,vpfx) serial_hw_data="E" justify(reading5) asm clrwdt delay_100ms(2) -- let the signal be transmitted and buffer empty xrf_sleep=high -- stop the radio ftre the original data sent return true end function -- MAIN PROGRAM START asm clrwdt asm sleep ; use the WDT to delay about 2s to ; boot the sensor and radio asm clrwdt -- now initialise the XRF to the system network ID and enable sleep mode -- clear out any pre-existing RX buffer var byte rxd while serial_hw_data_available loop -- check if data is ready for us rxd = serial_hw_data -- get the data end loop rxd=" " if true then ; loop is simply to enable/disable ; if you have no radio then use if false then asm clrwdt ; this setup routine for testing xrf_sleep=low ; setup the radio using AT commands print_string(serial_hw_data, line1) ; send +++ WaitOnOk print_string(serial_hw_data, line5) ; change network with ATID0AFF delay_100ms(1) print_string(serial_hw_data, line2) ; enable sleep mode with ATSM2 delay_100ms(1) print_string(serial_hw_data, line3) ; and use it - send commit delay_100ms(1) print_string(serial_hw_data, line4) ; ATDN we are done configuring delay_100ms(10) print_string(serial_hw_data,booted) delay_100ms(2) xrf_sleep=high -- power down the radio end if forever loop asm clrwdt if CCP1IF then CCP1IF=false; mincounter=mincounter+1 end if if mincounter >= cyclesmax-1 then mincounter=0 var bit b = get_volts() end if end loop