From: Nathael Pajani Date: Sat, 1 May 2021 19:35:46 +0000 (+0200) Subject: bq769x0 BMS support (tested, but to be completed) Working: Alerts, config, and readin... X-Git-Url: http://git.techno-innov.fr/?a=commitdiff_plain;h=f240303100a58e6964f50a3820e0da6d8500a369;p=soft%2Flpc82x%2Fcore bq769x0 BMS support (tested, but to be completed) Working: Alerts, config, and reading of voltages and coulomb counter. Todo : Cell balancing --- diff --git a/extdrv/bq769x0_bms.c b/extdrv/bq769x0_bms.c new file mode 100644 index 0000000..de04a45 --- /dev/null +++ b/extdrv/bq769x0_bms.c @@ -0,0 +1,543 @@ +/**************************************************************************** + * extdrv/bq769x0_bms.c + * + * + * Copyright 2020 Nathael Pajani + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + + +#include "core/system.h" +#include "lib/errno.h" +#include "lib/string.h" +#include "drivers/i2c.h" +#include "extdrv/bq769x0_bms.h" + + +/***************************************************************************** */ +/* Support for TMP101 temperature bmss from Texas Instrument */ +/***************************************************************************** */ +/* This driver is made for the TMP101 version of the chip, though there's few + * diferences between the TMP100 and TMP101 version. + * This driver does not handle the SMBus Alert command. + */ + + +enum bq769x0_internal_reg_numbers { + SYSTEM_STATUS = 0, + CELL_BALANCE_1, + CELL_BALANCE_2, + CELL_BALANCE_3, + SYS_CTRL_1, + SYS_CTRL_2, + PROTECT_1, + PROTECT_2, + PROTECT_3, + OVER_VOLTAGE_TRIP, + UNDER_VOLTAGE_TRIP, + CC_CFG, /* Coulomb Counter */ + VC1_HIGH, /* 0x0C */ + VC1_LOW, + /* VC2 to VC15 ... */ + BAT_HIGH = 0x2A, + BAT_LOW, + TS1_HIGH, + TS1_LOW, + /* TS2 and TS3 */ + CC_HIGH = 0x32, + CC_LOW, + ADC_GAIN_1 = 0x50, + ADC_OFFSET, + ADC_GAIN_2 = 0x59, + BQ769X0_LAST_REG, +}; + + +/* Aditional defines, not exported to userspace */ + +/* Sys Ctrl 1 */ +#define LOAD_PRESENT(x) ((x) & 0x80) +#define ADC_ENABLE (0x01 << 4) +#define TEMP_SEL_DIE (0 << 3) +#define TEMP_SEL_TSX (1 << 3) +#define SHUTDOWN_CMD_A (0x01) +#define SHUTDOWN_CMD_B (0x02) + +/* Sys Ctrl 2 */ +#define DELAYS_OFF_FOR_TESTING (0x01 << 7) +#define CC_ENABLE (0x01 << 6) +#define CC_ONE_SHOT (0x01 << 5) +#define DISCHARGE_ON (0x01 << 1) +#define CHARGE_ON (0x01 << 0) + +/* CC Config */ +/* The documentation asks for CC_CFG to be set to 0x19 upon startup */ +#define CC_CFG_DEFAULT 0x19 + + +/* Check the bms presence, return 1 if bms was found. + * This is a basic check, it could be anything with the same address ... + */ +int bq769x0_probe_bms(struct bq769x0_bms_conf* conf) +{ + char cmd_buf = (conf->addr | I2C_READ_BIT); + + /* Did we already probe the bms ? */ + /* 1 is the only valid value, all other values indicate an error state */ + if (conf->probe_ok != 1) { + conf->probe_ok = i2c_read(conf->bus_num, &cmd_buf, 1, NULL, NULL, 0); + msleep(10); + } + return conf->probe_ok; +} + + +/* Read registers from the BMS + * Return value(s): + * Upon successfull completion, returns 0 and the registers content is placed in the buffer. + * On error, returns a negative integer equivalent to errors from glibc. + */ +#define CMD_BUF_SIZE 3 +int bq769x0_bms_get_regs(struct bq769x0_bms_conf* conf, + uint8_t reg_start, uint8_t* values, uint8_t len) +{ + int ret = 0; + char cmd_buf[CMD_BUF_SIZE] = { conf->addr, 0, (conf->addr | I2C_READ_BIT), }; + char ctrl_buf[CMD_BUF_SIZE] = { I2C_CONT, I2C_DO_REPEATED_START, I2C_CONT, }; + + if (bq769x0_probe_bms(conf) != 1) { + return -ENODEV; + } + if (values == NULL) { + return -EINVAL; + } + if ((reg_start + len - 1) > BQ769X0_LAST_REG) { + return -EINVAL; + } + cmd_buf[1] = reg_start; + + ret = i2c_read(conf->bus_num, cmd_buf, CMD_BUF_SIZE, ctrl_buf, values, len); + if (ret != len) { + conf->probe_ok = 0; + return ret; + } + return 0; +} + +#define REG_WR_CMD_SIZE 2 +#define MAX_WR_BUF_SIZE (REG_WR_CMD_SIZE + CC_CFG) /* Maybe someone wants to write all regs at once ? */ +int bq769x0_bms_set_regs(struct bq769x0_bms_conf* conf, + uint8_t reg_start, uint8_t* values, uint8_t len) +{ + int ret = 0; + char cmd_buf[MAX_WR_BUF_SIZE] = { conf->addr, }; + + if (bq769x0_probe_bms(conf) != 1) { + return -ENODEV; + } + if (values == NULL) { + return -EINVAL; + } + if ((reg_start + len - 1) > CC_CFG) { /* CC_CFG is the last RW reg */ + return -EINVAL; + } + cmd_buf[1] = reg_start; + + memcpy((cmd_buf + REG_WR_CMD_SIZE), values, len); + ret = i2c_write(conf->bus_num, cmd_buf, (REG_WR_CMD_SIZE + len), NULL); + if (ret != (REG_WR_CMD_SIZE + len)) { + conf->probe_ok = 0; + return ret; + } + return 0; +} + +int bq769x0_bms_read_adc_gain(struct bq769x0_bms_conf* conf) +{ + int ret = 0; + uint8_t gain = 0, tmp = 0; + + /* Get GAIN two MSB bits */ + ret = bq769x0_bms_get_regs(conf, ADC_GAIN_1, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 6; + return -1; + } + msleep(2); + gain = (tmp & 0x0C) << 1; + + /* Get GAIN three LSB bits */ + ret = bq769x0_bms_get_regs(conf, ADC_GAIN_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 7; + return -1; + } + msleep(2); + gain |= ((tmp >> 5) & 0x07); + + /* Store gain and gain in µV */ + conf->adc_gain = gain; + conf->adc_gain_uV = 365 + gain; + + /* Get ADC offset */ + ret = bq769x0_bms_get_regs(conf, ADC_OFFSET, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 8; + return -1; + } + conf->adc_offset = (int8_t)tmp; + + return 0; +} + +/* Check load presence. + * This is valid only when the battery charging is disabled. + * Return 1 if load is present, or 0 if load is not present and charge is disabled. + * Returns -1 on error, or -2 if charge is enabled + */ +int bq769x0_bms_load_present(struct bq769x0_bms_conf* conf) +{ + int ret = 0; + uint8_t sys_ctrl[2]; + + ret = bq769x0_bms_get_regs(conf, SYS_CTRL_1, sys_ctrl, 2); + if (ret != 0) { + conf->probe_ok = 9; + return -1; + } + if (sys_ctrl[1] & CHARGE_ON) { + return -2; + } + if (LOAD_PRESENT(sys_ctrl[0])) { + return 1; + } + return 0; /* */ +} + +/* BMS Shut-Down. + * Disable both charge and discharge and turn off BMS by sending shutdown sequence. + * Only way out of sequence is power cycle (battery removal and re-insertion or button). + */ +int bq769x0_bms_shutdown(struct bq769x0_bms_conf* conf) +{ + int ret = 0; + uint8_t tmp = 0; + + /* Turn off both charge and discharge */ + tmp = 0; + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 10; + return ret; + } + msleep(2); + + /* Write shutdown sequence part 1 */ + tmp = SHUTDOWN_CMD_A; + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_1, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 11; + return ret; + } + msleep(2); + /* Write shutdown sequence part 2 */ + tmp = SHUTDOWN_CMD_B; + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_1, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 12; + return ret; + } + return 0; /* Shutdown OK ... should we reach this point ??? */ +} + +/* Start a conversion when the bms is not in Continuous CC mode. */ +int bq769x0_bms_start_conversion(struct bq769x0_bms_conf* conf) +{ + int ret = 0; + uint8_t tmp = 0; + + /* Read actual config */ + ret = bq769x0_bms_get_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 13; + return ret; + } + msleep(2); + /* Set CC one-shot conversion start bit and write back */ + tmp &= ~(CC_ENABLE); + tmp |= CC_ONE_SHOT; + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 14; + return ret; + } + + return 0; /* Conversion start success */ +} + +/* Place the bms in continuous CC convertion mode */ +int bq769x0_bms_set_continuous_conversion(struct bq769x0_bms_conf* conf) +{ + int ret = 0; + uint8_t tmp = 0; + + /* Read actual config */ + ret = bq769x0_bms_get_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 15; + return ret; + } + msleep(2); + /* Set CC continuous conversion bit and write back */ + tmp |= CC_ENABLE; + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 16; + return ret; + } + return 0; /* Configuration change success */ +} + +/* Enable/Disable charge and/or discharge + * Note that it is not possible to keep the old charge/discharge state and modify + * only the other one with this function. + */ +int bq769x0_bms_change_state(struct bq769x0_bms_conf* conf, uint8_t charge, uint8_t discharge) +{ + int ret = 0; + uint8_t tmp = 0; + + /* Read actual config */ + ret = bq769x0_bms_get_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 17; + return ret; + } + msleep(2); + /* Update required bits */ + tmp &= ~(CHARGE_ON | DISCHARGE_ON); + if (charge) { + tmp |= CHARGE_ON; + } + if (discharge) { + tmp |= DISCHARGE_ON; + } + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 18; + return ret; + } + return 0; /* Configuration change success */ +} + + +/* Set the uvlo (under voltage lockout), ovp (overvoltage protection), + * ocd (over curent in discharge) and scd (short circuit in discharge) values + * Note that timmings will be set to their minimum values. + * - scd is between 0 and 7, 0 is 22mV and 7 is 100mV with approx 11mV steps. + * - ocd is between 0 and 15, 0 is 8mV and 15 is 50mV with approx 3mV steps. + * - uvlo and ovp are the middle 8 bits of the 13 bits of ADC values with the corresponding mapping : + * 0b(10 1000) which is between 8200 and 12280 + * 0b(01 0000) which is between 4096 and 8176 + * The uvlo and ovp values depend on ADC gain and ADC offset which are stored in + * conf->adc_gain_uV and conf->adc_offset upon configuration. + */ +int bq769x0_bms_set_ranges(struct bq769x0_bms_conf* conf, + uint8_t uvlo, uint8_t ovp, uint8_t ocd, uint8_t scd) +{ + int ret = 0; + uint8_t protect[5]; + + if ((scd > 0x07) || (ocd > 0x0F)) { + return -EINVAL; + } + protect[0] = scd & 0x07; + protect[1] = ocd & 0x0F; + protect[2] = 0; + protect[3] = ovp; + protect[4] = uvlo; + ret = bq769x0_bms_set_regs(conf, PROTECT_1, protect, 5); + if (ret != 0) { + conf->probe_ok = 19; + return ret; + } + return 0; +} + + +/* Read ADC values. + * type is one of : + * - ALL : Read all values (up to nb_val) + * - ALL_CELLS : Read all cells values. nb_val must equal conf->nb_cells (max 15) + * (number of available cells depends on package) + * - BATTERY : Read only the battery voltage (nb_val must be 1) + * - THERMISTORS : Read only the Thermistor values. nb_val between 1 and 3 and + * must equal conf->nb_thermistors + * (number of available thermistors depends on package) + * - CELL(x) : Read only cell "x" voltage + * - CC_COUNT : Read the Coulomb counter value (nb_val must be 1) + */ +int bq769x0_bms_read_adc(struct bq769x0_bms_conf* conf, uint16_t* data, int type, int nb_val) +{ + int ret = 0, i = 0; + uint8_t offset = 0; + + if ((nb_val == 0) || (data == NULL)) { + return -EINVAL; + } + + switch (type) { + case BATTERY: + offset = BAT_HIGH; + if (nb_val != 1) { + return -EINVAL; + } + break; + case ALL_CELLS: + if (nb_val != conf->nb_cells) { + return -EINVAL; + } + /* Breakthrough */ + case ALL: + offset = VC1_HIGH; + break; + case THERMISTORS: + offset = TS1_HIGH; + if (nb_val > conf->nb_thermistors) { + return -EINVAL; + } + break; + case CC_COUNT: + offset = CC_HIGH; + if (nb_val != 1) { + return -EINVAL; + } + break; + default: + offset = VC1_HIGH + (2 * (type - 10)); + if (nb_val != 1) { + return -EINVAL; + } + break; + } + ret = bq769x0_bms_get_regs(conf, offset, (uint8_t*)data, (nb_val * 2)); + if (ret != 0) { + return ret; + } + + /* Change endianness */ + for (i = 0; i < nb_val; i++) { + data[i] = (uint16_t)byte_swap_16(data[i]); + } + return 0; +} + +int bq769x0_bms_get_and_erase_status(struct bq769x0_bms_conf* conf, uint8_t* status) +{ + int ret = 0; + + if (status == NULL) { + return -EINVAL; + } + /* Read status */ + ret = bq769x0_bms_get_regs(conf, SYSTEM_STATUS, status, 1); + if (ret != 0) { + return ret; + } + msleep(2); + /* Write back to clear all bits */ + ret = bq769x0_bms_set_regs(conf, SYSTEM_STATUS, status, 1); + if (ret != 0) { + return ret; + } + return 0; +} + + +/* BMS config + * Performs default configuration of the bms. + * Return value : + * Upon successfull completion, returns 0. On error, returns a negative integer + * equivalent to errors from glibc. + */ +#define CONF_BUF_SIZE 4 +int bq769x0_bms_config(struct bq769x0_bms_conf* conf, uint8_t* old_status) +{ + int ret = 0; + uint8_t tmp = 0; + + /* Probe BMS and get old status */ + ret = bq769x0_bms_get_and_erase_status(conf, &tmp); + if (ret != 0) { + conf->probe_ok = 2; + return ret; + } + if (old_status != NULL) { + *old_status = tmp; + } + msleep(2); + + /* Programm CC_CFG as requested in datasheet */ + tmp = CC_CFG_DEFAULT; + ret = bq769x0_bms_set_regs(conf, CC_CFG, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 3; + return ret; + } + msleep(2); + + /* Program first control register : + * - enable ADC + * - set temp readings to externel thermistor instead of Die temperature + */ + tmp = ADC_ENABLE | TEMP_SEL_TSX; + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_1, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 4; + return ret; + } + + msleep(2); + /* Program second control register : + * - Enable coulomb counter + * - Enable discharge + * - Enable charge + */ + tmp = CC_ENABLE | DISCHARGE_ON | CHARGE_ON; + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 5; + return ret; + } + msleep(2); + + /* Re-read (and discard) status so that any new status read reflects a fresh status */ + ret = bq769x0_bms_get_and_erase_status(conf, &tmp); + if (ret != 0) { + return ret; + } + msleep(2); + + /* Get the ADC gain and offset once upon startup */ + ret = bq769x0_bms_read_adc_gain(conf); + if (ret != 0) { + return ret; + } + + return 0; /* Config success */ +} + diff --git a/include/extdrv/bq769x0_bms.h b/include/extdrv/bq769x0_bms.h new file mode 100644 index 0000000..ad523cb --- /dev/null +++ b/include/extdrv/bq769x0_bms.h @@ -0,0 +1,162 @@ +/**************************************************************************** + * extdrv/bq769x0_bms.h + * + * + * Copyright 2020 Nathael Pajani + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + *************************************************************************** */ + +#ifndef EXTDRV_BQ769X0_H +#define EXTDRV_BQ769X0_H + +#include "lib/stdint.h" + + +/***************************************************************************** */ +/* Support for BQ769X0 Battery Protection from Texas Instrument */ +/***************************************************************************** */ +/* This driver is made for the BQ76920 version of the chip, though there's few + * diferences between the BQ76920, BQ76930 and BQ76950 versions. + * This driver does not handle the Alert command. + */ + +/* BQ769X0 instance data. + * Use one of this for each BMS you want to access. + * - addr is the BMS address on most significant bits. + */ +struct bq769x0_bms_conf { + uint8_t addr; + uint8_t bus_num; + uint8_t probe_ok; + uint8_t crc_check; /* 0 : No CRC check */ + uint8_t adc_gain; + int8_t adc_offset; + uint16_t adc_gain_uV; /* ADC gain converted to µV */ + uint8_t nb_cells; + uint8_t nb_thermistors; +}; + +/* Check the bms presence, return 1 if found + * This is a basic check, it could be anything with the same address ... + */ +int bq769x0_probe_bms(struct bq769x0_bms_conf* conf); + + +int bq769x0_bms_get_regs(struct bq769x0_bms_conf* conf, + uint8_t reg_start, uint8_t* values, uint8_t len); + +int bq769x0_bms_set_regs(struct bq769x0_bms_conf* conf, + uint8_t reg_start, uint8_t* values, uint8_t len); + + +/* Read the BMS status Word and erase it's content + * conf: the bms configuration structure. + * status: pointer to an uint8_t value to store the old status value. May NOT be NULL. + * Return value: + * Upon successfull completion, returns 0. On error, returns a negative integer + * equivalent to errors from glibc. + */ +int bq769x0_bms_get_and_erase_status(struct bq769x0_bms_conf* conf, uint8_t* status); + +/* SYS Status */ +#define CC_READY(x) ((x) & 0x80) /* New Coulomb Counter reading available */ +#define DEVICE_NOT_READY(x) ((x) & 0x20) /* Set means internal fault */ +#define EXT_OVERRIDE_ALERT(x) ((x) & 0x10) +#define UNDER_VOLTAGE(x) ((x) & 0x08) /* UV */ +#define OVER_VOLTAGE(x) ((x) & 0x04) /* OV */ +#define SHORT_CIRCUIT_DISCHARGE(x) ((x) & 0x02) /* SCD */ +#define OVER_CURRENT_DISCHARGE(x) ((x) & 0x01) /* OCD */ + + + +/* Check load presence. + * This is valid only when the battery charging is disabled. + * Return 1 if load is present, or 0 if load is not present and charge is disabled. + * Returns -1 on error, or -2 if charge is enabled + */ +int bq769x0_bms_load_present(struct bq769x0_bms_conf* conf); + +/* BMS Shut-Down. + * Disable both charge and discharge and turn off BMS by sending shutdown sequence. + * Only way out of sequence is power cycle (battery removal and re-insertion or button). + */ +int bq769x0_bms_shutdown(struct bq769x0_bms_conf* conf); + +/* Start a conversion when the bms is not in Continuous CC mode. */ +int bq769x0_bms_start_conversion(struct bq769x0_bms_conf* conf); + +/* Place the bms in continuous CC convertion mode */ +int bq769x0_bms_set_continuous_conversion(struct bq769x0_bms_conf* conf); + +/* Enable/Disable charge and/or discharge + * Note that it is not possible to keep the old charge/discharge state and modify + * only the other one with this function. + */ +int bq769x0_bms_change_state(struct bq769x0_bms_conf* conf, uint8_t charge, uint8_t discharge); + +/* Set the uvlo (under voltage lockout), ovp (overvoltage protection), + * ocd (over curent in discharge) and scd (short circuit in discharge) values + * Note that timmings will be set to their minimum values. + * - scd is between 0 and 7, 0 is 22mV and 7 is 100mV with approx 11mV steps. + * - ocd is between 0 and 15, 0 is 8mV and 15 is 50mV with approx 3mV steps. + * - uvlo and ovp are the middle 8 bits of the 13 bits of ADC values with the corresponding mapping : + * 0b(10 1000) which is between 8200 and 12280 + * 0b(01 0000) which is between 4096 and 8176 + * The uvlo and ovp values depend on ADC gain and ADC offset which are stored in + * conf->adc_gain_uV and conf->adc_offset upon configuration. + */ +int bq769x0_bms_set_ranges(struct bq769x0_bms_conf* conf, + uint8_t uvlo, uint8_t ovp, uint8_t ocd, uint8_t scd); + + + +enum bq769x0_bms_adc_values { + ALL = 0, + BATTERY, + ALL_CELLS, + THERMISTORS, + CC_COUNT, +}; +#define CELL(x) ((x) + 10) + +/* Read ADC values. + * type is one of : + * - ALL : Read all values (up to nb_val) + * - ALL_CELLS : Read all cells values. nb_val must equal conf->nb_cells (max 15) + * (number of available cells depends on package) + * - BATTERY : Read only the battery voltage (nb_val must be 1) + * - THERMISTORS : Read only the Thermistor values. nb_val between 1 and 3 and + * must equal conf->nb_thermistors + * (number of available thermistors depends on package) + * - CELL(x) : Read only cell "x" voltage + * - CC_COUNT : Read the Coulomb counter value (nb_val must be 1) + */ +int bq769x0_bms_read_adc(struct bq769x0_bms_conf* conf, uint16_t* data, int type, int nb_val); + + +/* BMS config + * Performs default configuration of the BMS. + * conf: the bms configuration structure. + * old_status: pointer to an uint8_t value to store the old status value. May be NULL. + * Return value: + * Upon successfull completion, returns 0. On error, returns a negative integer + * equivalent to errors from glibc. + */ +int bq769x0_bms_config(struct bq769x0_bms_conf* conf, uint8_t* old_status); + +#endif /* EXTDRV_BQ769X0_H */ +