Initial part of BQ769x0 BMS support
authorNathael Pajani <nathael.pajani@ed3l.fr>
Sun, 6 Dec 2020 17:46:17 +0000 (18:46 +0100)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Tue, 8 Nov 2022 16:03:05 +0000 (17:03 +0100)
extdrv/bq769x0_bms.c [new file with mode: 0644]
include/extdrv/bq769x0_bms.h [new file with mode: 0644]

diff --git a/extdrv/bq769x0_bms.c b/extdrv/bq769x0_bms.c
new file mode 100644 (file)
index 0000000..fbf9c93
--- /dev/null
@@ -0,0 +1,308 @@
+/****************************************************************************
+ *   extdrv/bq769x0_bms.c
+ *
+ *
+ * Copyright 2020 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+
+#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 (file)
index 0000000..e4d3ccc
--- /dev/null
@@ -0,0 +1,81 @@
+/****************************************************************************
+ *   extdrv/bq769x0_bms.h
+ *
+ *
+ * Copyright 2020 Nathael Pajani <nathael.pajani@ed3l.fr>
+ *
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ *************************************************************************** */
+
+#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 */
+