Last active
July 19, 2022 13:37
-
-
Save stokebrain/e7956dc7b5f1f62f3a54e1a820042526 to your computer and use it in GitHub Desktop.
BQ27441 battery fuel gauge for Espruino
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This file implements code to read data from and set data for | |
* TI-make BQ27441 battery fuel gauge. Data to be set include | |
* "Design Capacity", "Termination Voltage" etc. Data to be read | |
* include "Voltage", "Current", "Temperature", "SOC", etc. | |
*/ | |
var i2c = I2C1; | |
/* | |
* File: main.c | |
* Author: Chintan | |
* | |
* Created on November 17, 2015, 5:44 PM | |
*/ | |
var ADDRESS = 0x55; // taken from datasheet - page 13 | |
function CHECK_BIT (val, pos) { return ((val) & (1<<(pos))); } | |
var CONTROL_STATUS = 0x0000; | |
var DEVICE_TYPE = 0x0001; | |
var FW_VERSION = 0x0002; | |
var DM_CODE = 0x0004; | |
var PREV_MACWRITE = 0x0007; | |
var CHEM_ID = 0x0008; | |
var BAT_INSERT = 0x000C; | |
var BAT_REMOVE = 0x000D; | |
var SET_HIBERNATE = 0x0011; | |
var CLEAR_HIBERNATE = 0x0012; | |
var SET_CFGUPDATE = 0x0013; | |
var SHUTDOWN_ENABLE = 0x001B; | |
var SHUTDOWN = 0x001C; | |
var SEALED = 0x0020; | |
var PULSE_SOC_INT = 0x0023; | |
var RESET = 0x0041; | |
var SOFT_RESET = 0x0042; | |
var CONTROL_1 = 0x00; | |
var CONTROL_2 = 0x01; | |
var TEMPERATURE = 0x02; | |
var VOLTAGE = 0x04; | |
var FLAGS = 0x06; | |
var NOMINAL_AVAIL_CAPACITY = 0x08; | |
var FULL_AVAIL_CAPACITY = 0x0a; | |
var REMAINING_CAPACITY = 0x0c; | |
var FULL_CHG_CAPACITY = 0x0e; | |
var AVG_CURRENT = 0x10; | |
var STANDBY_CURRENT = 0x12; | |
var MAXLOAD_CURRENT = 0x14; | |
var AVERAGE_POWER = 0x18; | |
var STATE_OF_CHARGE = 0x1c; | |
var INT_TEMPERATURE = 0x1e; | |
var STATE_OF_HEALTH = 0x20; | |
var BLOCK_DATA_CHECKSUM = 0x60; | |
var BLOCK_DATA_CONTROL = 0x61; | |
var DATA_BLOCK_CLASS = 0x3E; | |
var DATA_BLOCK = 0x3F; | |
var DESIGN_CAPACITY_1 = 0x4A; | |
var DESIGN_CAPACITY_2 = 0x4B; | |
var DESIGN_ENERGY = 0x4C; | |
var TERMINATE_VOLTAGE = 0x50; | |
var BATTERY_LOW = 15; | |
var BATTERY_FULL = 100; | |
var MAX_REGS = 0x7F; | |
// var deviceDescriptor; | |
/* This function initializes the I2C device*/ | |
// function init_i2c (DeviceName) { | |
// print("Initialising i2c device \n"); | |
// deviceDescriptor = open(DeviceName, O_RDWR); | |
// if (deviceDescriptor == -1) { | |
// print("Error opening device '%s' \n", DeviceName); | |
// process.exit(0); | |
// } | |
// } | |
/* This function sends data to the I2C device*/ | |
function I2cSendData (addr, data, len) { | |
// if (ioctl(deviceDescriptor, I2C_SLAVE, addr)) { | |
// print("I2cSendData_device : IOCTL Problem \n"); | |
// } | |
// write(deviceDescriptor, data, len); | |
i2c.writeTo(addr, data, len); | |
} | |
/* This function reads data from the I2C device*/ | |
function I2cReadData (addr, data, len) { | |
// if (ioctl(deviceDescriptor, I2C_SLAVE, addr)) { | |
// print("I2cReadData_device : IOCTL Problem \n"); | |
// } | |
// read(deviceDescriptor, data, len); | |
i2c.readFrom(addr, data, len); | |
} | |
/* Convert the number to hexadecimal representation */ | |
function to_hex_16 (output, n) { | |
hex_digits = "0123456789abcdef"; | |
output[0] = hex_digits[(n >> 12) & 0xF]; | |
output[1] = hex_digits[(n >> 8) & 0xF]; | |
output[2] = hex_digits[(n >> 4) & 0xF]; | |
output[3] = hex_digits[(n & 0xF)]; | |
output[4] = '\0'; | |
} | |
function checksum(check_data) { | |
var sum = 0; | |
var ii = 0; | |
for (ii = 0; ii < 32; ii++) { | |
sum += check_data[ii+62]; | |
} | |
sum &= 0xFF; | |
return 0xFF - sum; | |
} | |
/* getliner() reads one line from standard input and copies it to line array | |
* (but no more than max chars) | |
* It does not place the terminating \n line array. | |
* Returns line length, or 0 for empty line, or EOF for end-of-file. | |
*/ | |
function getliner (line, max) { | |
var nch = 0; | |
var c; | |
max = max - 1; /* Leave room for '\0' */ | |
while ((c = getchar()) != EOF) { | |
if (c == '\n') { | |
break; | |
} | |
if (nch < max) { | |
line[nch] = c; | |
nch = nch + 1; | |
} | |
} | |
if (c == EOF && nch == 0) { | |
return EOF; | |
} | |
line[nch] = '\0'; | |
return nch; | |
} | |
function config_gauge () { | |
var unseal_data = Array(10), cfgupdate_data = Array(10), flag_data = Array(10), flag_out = Array(10); | |
var block_data_control = Array(10), data_block_class = Array(10), data_block = Array(10), block_data_checksum = Array(10); | |
var block_data_checksum_data = Array(10), design_capacity_loc = Array(10), design_capacity_data = Array(10); | |
var design_energy_data = Array(10), terminate_voltage_data = Array(10), des_cap = Array(10), term_vol = Array(10); | |
var design_energy_loc = Array(10), terminate_voltage_loc = Array(10), init_reg = Array(100), init_data = Array(100); | |
var soft_reset = Array(10), seal_data = Array(10); | |
var i, design_capacity, new_design_capacity, new_design_cap_hex, design_energy; | |
var cksum = 0, terminate_voltage,new_terminate_voltage; | |
var new_design_cap = Array(7), a = Array(10), b = Array(10), tmp = Array(10), new_term_vol = Array(7); | |
init_reg[0] = 0x00; | |
init_reg[1] = 0x04; | |
unseal_data[0] = CONTROL_1; | |
unseal_data[1] = 0x00; | |
unseal_data[2] = 0x80; | |
cfgupdate_data[0] = CONTROL_1; | |
cfgupdate_data[1] = 0x13; | |
cfgupdate_data[2] = 0x00; | |
flag_data[0] = FLAGS; | |
block_data_control[0] = BLOCK_DATA_CONTROL; | |
block_data_control[1] = 0x00; | |
data_block_class[0] = DATA_BLOCK_CLASS; | |
data_block_class[1] = 0x52; | |
data_block[0] = DATA_BLOCK; | |
data_block[1] = 0x00; | |
block_data_checksum[0] = BLOCK_DATA_CHECKSUM; | |
design_capacity_loc[0] = DESIGN_CAPACITY_1; | |
design_energy_loc[0] = DESIGN_ENERGY; | |
terminate_voltage_loc[0] = TERMINATE_VOLTAGE; | |
soft_reset[0] = 0x00; | |
soft_reset[1] = 0x42; | |
soft_reset[2] = 0x00; | |
seal_data[0] = 0x00; | |
seal_data[1] = 0x20; | |
seal_data[2] = 0x00; | |
/* A cursory read of all registers*/ | |
I2cSendData(ADDRESS, init_reg, 2); | |
I2cReadData(ADDRESS, init_data, 100); | |
/* Unseal the gauge - Refer TRM - Pg-14 */ | |
I2cSendData(ADDRESS, unseal_data, 3); // #1 | |
I2cSendData(ADDRESS, unseal_data, 3); | |
delay(5); | |
print("The gauge seems to be unsealed. \n"); | |
I2cSendData(ADDRESS, cfgupdate_data, 3); // #2 | |
delay(1000); | |
I2cSendData(ADDRESS, flag_data, 1); // #3 | |
delay(5); | |
I2cReadData(ADDRESS, flag_out, 1); | |
print("The flag_out is: %x \n", flag_out[0]); | |
if (! CHECK_BIT(flag_out[0], 4)) { | |
print("Cannot proceed with configuration. \n"); | |
print("The CFGUPDATE MODE has not been enabled yet. \n"); | |
return; | |
} | |
print("The gauge is ready to be configured \n"); | |
I2cSendData(ADDRESS, block_data_control, 2); // #4 | |
delay(5); | |
I2cSendData(ADDRESS, data_block_class, 2); // #5 | |
delay(5); | |
I2cSendData(ADDRESS, data_block, 2); // #6 | |
delay(5); | |
I2cSendData(ADDRESS, block_data_checksum, 1); // #7 | |
delay(5); | |
I2cReadData(ADDRESS, block_data_checksum_data, 1); | |
delay(5); | |
print("The checksum_data: %x \n", block_data_checksum_data[0]); | |
// print("The checksum is as expected. Config will proceed. \n"); | |
/* Design Capacity */ | |
I2cSendData(ADDRESS, design_capacity_loc, 1); // #8 | |
delay(5); | |
I2cReadData(ADDRESS, design_capacity_data, 2); | |
delay(5); | |
//print("Design capacity data: %x and %x \n", design_capacity_data[0], design_capacity_data[1]); | |
design_capacity = design_capacity_data[0]*16*16 + design_capacity_data[1]; | |
delay(5); | |
print("The current design capacity is: "+ design_capacity +" mAh \n"); | |
print("Set new design capacity in mAh (ENTER to continue) ? "); | |
getliner(new_design_cap, 7); | |
if (new_design_cap != EOF && new_design_cap[0] != 0) { | |
print("Trying to update the design capacity \n"); | |
design_capacity = atoi(new_design_cap); | |
if (design_capacity < 0) { | |
design_capacity = 0; | |
} | |
else if (design_capacity > 8000) { | |
design_capacity = 8000; // #9 | |
} | |
print("Trying to set new design capacity to: "+design_capacity+" \n"); | |
to_hex_16(tmp, design_capacity); | |
for(i = 0; i <= 3; i++) { | |
print("Output at position "+i+" has "+tmp[i]+" \n"); | |
} | |
des_cap[0] = DESIGN_CAPACITY_1;//design_capacity_loc[0]; | |
des_cap[1] = (tmp[0] - '0')*16 + (tmp[1] - '0'); | |
des_cap[2] = (tmp[2] - '0')*16 + (tmp[3] - '0'); | |
print("Des cap 0: "+des_cap[0]+" "); | |
print("Des cap 1: "+des_cap[1]+" "); | |
print("Des cap 2: "+des_cap[2]+" "); | |
I2cSendData(ADDRESS, des_cap, 3); | |
delay(1000); | |
} else { | |
print("Design capacity left unchanged. Now at "+design_capacity+" mAh \n"); | |
} | |
/* Design Energy */ | |
I2cSendData(ADDRESS, design_energy_loc, 1); // #8 | |
delay(50); | |
I2cReadData(ADDRESS, design_energy_data, 2); | |
delay(50); | |
design_energy = design_energy_data[0]*16*16 + design_energy_data[1]; | |
print("The current design energy is: "+design_energy+" mWh \n"); | |
print("Setting the design energy as 3.8 times the design capacity. \n"); | |
design_energy = design_capacity * 3.8; // standard G1B conversion formula | |
delay(100); | |
print("The new design energy is: "+design_energy+" mWh \n "); | |
to_hex_16(tmp, new_design_capacity); | |
design_energy_data[0] = DESIGN_ENERGY; | |
design_energy_data[1] = (tmp[0] - '0')*16 + (tmp[1] - '0'); | |
design_energy_data[2] = (tmp[2] - '0')*16 + (tmp[3] - '0'); | |
I2cSendData(ADDRESS, design_energy_data, 3); | |
delay(5); | |
/* Terminate Voltage */ | |
I2cSendData(ADDRESS, terminate_voltage_loc, 1); // #8 | |
delay(5); | |
I2cReadData(ADDRESS, terminate_voltage_data, 2); | |
delay(5); | |
terminate_voltage = terminate_voltage_data[0]*16*16 + terminate_voltage_data[1]; | |
print("The terminate voltage is: "+terminate_voltage+" mV \n"); | |
print("Set new terminate voltage in mV between 2500 mV and 3700 mV (ENTER to continue) ? "); | |
getliner(new_term_vol, 7); | |
if (new_term_vol != EOF && new_term_vol[0] != 0) { | |
print("Trying to update the terminate voltage \n"); | |
delay(100); | |
terminate_voltage = atoi(new_term_vol); | |
if (terminate_voltage > 3700) { | |
terminate_voltage = 3700; | |
} | |
else if (terminate_voltage < 2500) { | |
terminate_voltage = 2500; // #9 | |
} | |
print("Trying to set new terminate voltage to: "+terminate_voltage+" \n"); | |
to_hex_16(tmp, terminate_voltage); | |
term_vol[0] = TERMINATE_VOLTAGE;//design_capacity_loc[0]; | |
term_vol[1] = (tmp[0] - '0')*16 + (tmp[1] - '0'); | |
term_vol[2] = (tmp[2] - '0')*16 + (tmp[3] - '0'); | |
I2cSendData(ADDRESS, term_vol, 3); | |
delay(1000); | |
} else { | |
print("Terminate Voltage left unchanged. Now at "+terminate_voltage+" mV \n"); | |
} | |
/* Finishing up with the configuration */ | |
cksum = checksum(init_data); // #10 | |
delay(1000); | |
print("New Checksum found is: "+cksum+" \n"); | |
block_data_checksum[1] = cksum; // #11 | |
I2cSendData(ADDRESS, block_data_checksum, 2); | |
delay(1000); | |
I2cSendData(ADDRESS, soft_reset, 3); // #12 | |
delay(500); | |
//print("Design Cap data 0: %x", data[72]); | |
//print("Design Cap data :1 %x", data[73]); | |
//design_capacity = data[72]*16*16 + data[73]; | |
I2cSendData(ADDRESS, flag_data, 1); // #13 | |
delay(500); | |
I2cReadData(ADDRESS, flag_out, 1); | |
delay(500); | |
print("The flag_out is: "+flag_out[0]+" \n"); | |
if (!CHECK_BIT(flag_out[0], 4)) { | |
print("CFGUPDTE has been exited, configuration done. \n"); | |
I2cSendData(ADDRESS, seal_data, 1); // #14 | |
delay(5); | |
print("Gauge has been sealed and is ready for operation \n"); | |
} | |
} | |
function main(argc, argv) { | |
var i, voltage; | |
var data = Array(100); | |
var writeData = Array(100); | |
var remaining_batt_cap = 0.0; | |
var full_charge_cap = 0.0; | |
var soc = 0.0; | |
var temp = 0.0; | |
var current = 0.0; | |
writeData[0] = 0x00; | |
writeData[1] = 0x04; | |
print("Inside main \n"); | |
// init_i2c("/dev/i2c-1"); | |
i2c.setup({ sda:B7, scl:B6 }); | |
config_gauge(); | |
while (true) { | |
/* Reading the device registers */ | |
I2cSendData(ADDRESS, writeData, 2); | |
I2cReadData(ADDRESS, data, 100); | |
voltage = data[4]*16*16 + data[3]; | |
remaining_batt_cap = data[12]*16*16 + data[11]; | |
full_charge_cap = data[14]*16*16 + data[13]; | |
soc = data[28]*16*16 + data[27];//(remaining_batt_cap/full_charge_cap)*100; | |
temp = (data[2]*16*16 + data[1])/10.0 - 273.0; | |
current = data[16]*16*16 + data[15]; | |
if (current >= 32267) { | |
current = current - 65536; // two's complement as signed integer | |
} | |
print("Voltage: "+voltage+" mV\n"); | |
print("Current: "+current+" mA\n"); | |
print("Remaining Battery Capacity: "+remaining_batt_cap+" mAh\n"); | |
print("Full Charge Capacity: "+full_charge_cap+" mAh\n"); | |
print("State of Charge: "+soc+" p.c.\n"); | |
print("Temperature: "+temp+" Deg C\n"); | |
delay(10000); | |
} | |
// close(deviceDescriptor); | |
// endwin(); | |
return; | |
} | |
function delay(time, cb) { | |
var now = Date.now(); | |
while (Date.now() < now + time * 1000) {} | |
return; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment