From e14fa1c471a790bdbd047b72781ab2703fd0e29d Mon Sep 17 00:00:00 2001 From: Nathael Pajani Date: Sun, 6 Dec 2020 18:46:17 +0100 Subject: [PATCH] Initial part of BQ769x0 BMS support --- extdrv/bq769x0_bms.c | 308 +++++++++++++++++++++++++++++++++++ include/extdrv/bq769x0_bms.h | 81 +++++++++ 2 files changed, 389 insertions(+) create mode 100644 extdrv/bq769x0_bms.c create mode 100644 include/extdrv/bq769x0_bms.h diff --git a/extdrv/bq769x0_bms.c b/extdrv/bq769x0_bms.c new file mode 100644 index 0000000..fbf9c93 --- /dev/null +++ b/extdrv/bq769x0_bms.c @@ -0,0 +1,308 @@ +/**************************************************************************** + * 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 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) & 0x20) /* UV */ +#define OVER_VOLTAGE(x) ((x) & 0x20) /* OV */ +#define SHORT_CIRCUIT_DISCHARGE(x) ((x) & 0x20) /* SCD */ +#define OVER_CURRENT_DISCHARGE(x) ((x) & 0x20) /* OCD */ + + +/* 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 0x019 + + +/* 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 ? */ + if (conf->probe_ok != 1) { + conf->probe_ok = i2c_read(conf->bus_num, &cmd_buf, 1, NULL, NULL, 0); + } + 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 +static 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; +} + +/* Start a conversion when the bms is not in Continuous CC mode. */ +int bq769x0_bms_start_conversion(struct bq769x0_bms_conf* conf) +{ + return 0; /* Conversion start success */ +} + +/* Place the bms in continuous CC convertion mode */ +int bq769x0_bms_set_continuous_conversion(struct bq769x0_bms_conf* conf) +{ + return 0; /* Configuration change success */ +} + + +static void 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; + } + 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; + } + 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; + } + conf->adc_offset = (int8_t)tmp; +} + +int bq769x0_bms_read_adc(struct bq769x0_bms_conf* conf, uint16_t* data, int offset, int nb_val) +{ + + return 0; +} + +int bq769x0_bms_get_and_erase_status(struct bq769x0_bms_conf* conf, uint8_t* status) +{ + int ret = 0; + uint8_t tmp = 0; + + /* Store the new configuration */ + ret = bq769x0_bms_get_regs(conf, SYSTEM_STATUS, status, 1); + if (ret != 0) { + return ret; + } + ret = bq769x0_bms_set_regs(conf, SYSTEM_STATUS, &tmp, 1); + if (ret != 0) { + return ret; + } + return 0; +} + + +/* Sensor 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; + + /* Store the new configuration */ + 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; + } + + /* 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; + } + + /* 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; + } + + /* Program second control register : + * - FIXME : Change delays to test mode, should be removed in production + * - Enable coulomb counter + * - Enable discharge + * - Enable charge + */ +// tmp = DELAYS_OFF_FOR_TESTING | CC_ENABLE | DISCHARGE_ON | CHARGE_ON; + tmp = DELAYS_OFF_FOR_TESTING | DISCHARGE_ON | CHARGE_ON; + ret = bq769x0_bms_set_regs(conf, SYS_CTRL_2, &tmp, 1); + if (ret != 0) { + conf->probe_ok = 5; + return ret; + } + bq769x0_bms_get_and_erase_status(conf, &tmp); + + /* Get the ADC gain and offset once upon startup */ + bq769x0_bms_read_adc_gain(conf); + + return 0; /* Config success */ +} + diff --git a/include/extdrv/bq769x0_bms.h b/include/extdrv/bq769x0_bms.h new file mode 100644 index 0000000..e4d3ccc --- /dev/null +++ b/include/extdrv/bq769x0_bms.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * 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 */ +}; + +/* 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); + + +/* 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); + +int bq769x0_bms_set_regs(struct bq769x0_bms_conf* conf, + uint8_t reg_start, uint8_t* values, uint8_t len); + + +/* 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 */ + -- 2.43.0