Acett SAM : board v01 support
authorNathael Pajani <nathael.pajani@ed3l.fr>
Sat, 1 May 2021 19:37:31 +0000 (21:37 +0200)
committerNathael Pajani <nathael.pajani@ed3l.fr>
Sat, 1 May 2021 19:37:31 +0000 (21:37 +0200)
apps/acett/v01/Makefile [new file with mode: 0644]
apps/acett/v01/main.c [new file with mode: 0644]

diff --git a/apps/acett/v01/Makefile b/apps/acett/v01/Makefile
new file mode 100644 (file)
index 0000000..da44d1f
--- /dev/null
@@ -0,0 +1,12 @@
+# Makefile for "base" apps
+
+MODULE = $(shell basename $(shell cd .. && pwd && cd -))
+NAME = $(shell basename $(CURDIR))
+
+.PHONY: $(NAME).bin
+$(NAME).bin:
+       @make -C ../../.. --no-print-directory NAME=$(NAME) MODULE=$(MODULE) apps/$(MODULE)/$(NAME)/$@
+
+clean mrproper:
+       @make -C ../../.. --no-print-directory $@
+
diff --git a/apps/acett/v01/main.c b/apps/acett/v01/main.c
new file mode 100644 (file)
index 0000000..85e3016
--- /dev/null
@@ -0,0 +1,555 @@
+/****************************************************************************
+ *   apps/ledstrip/main.c
+ *
+ * WS2812 Chainable leds example using Adafruit les strip
+ *
+ * Copyright 2013-2015 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 2 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 "lib/stdint.h"
+#include "core/system.h"
+#include "core/systick.h"
+#include "core/pio.h"
+#include "core/iap.h"
+#include "lib/stdio.h"
+#include "lib/errno.h"
+#include "lib/utils.h"
+#include "drivers/serial.h"
+#include "drivers/gpio.h"
+#include "drivers/i2c.h"
+
+#include "extdrv/bq769x0_bms.h"
+
+#define MODULE_VERSION    0x01
+#define MODULE_NAME "Bulle BMS"
+
+#define SELECTED_FREQ  FREQ_SEL_36MHz
+
+
+/***************************************************************************** */
+#define DEBUG 1
+
+#if defined DEBUG && DEBUG == 1
+#define debug_uprintf(...) \
+  if (verbose >= 2) { uprintf(UART0, __VA_ARGS__ ); }
+#else
+#define debug_uprintf(...) do {} while (0)
+#endif
+
+static uint8_t verbose = 2; /* Start with full verbosity for possible debug */
+
+
+/***************************************************************************** */
+/* Initial Pins configuration */
+const struct pio_config common_pins[] = {
+       /* UART 0 */
+       { LPC_GPIO_0_0, LPC_UART0_RX, 0 },
+       { LPC_GPIO_0_4, LPC_UART0_TX, 0 },
+       /* UART 1 */
+       { LPC_GPIO_0_23, LPC_UART1_RX, 0 },
+       { LPC_GPIO_0_17, LPC_UART1_TX, 0 },
+       /* I2C 0 */
+       { LPC_I2C0_SCL_PIO_0_10, LPC_FIXED, 0 },
+       { LPC_I2C0_SDA_PIO_0_11, LPC_FIXED, 0 },
+       /* GPIO */
+       { LPC_GPIO_0_2, LPC_GPIO, 0 },  /* Led */
+       { LPC_GPIO_0_13, LPC_GPIO, 0 }, /* Battery Alert */
+       ARRAY_LAST_PIO,
+};
+
+
+const struct pio alert = LPC_GPIO_0_13; /* Alert to/from BMS */
+const struct pio led = LPC_GPIO_0_2; /* Led status */
+
+/***************************************************************************** */
+void system_init()
+{
+       /* FIXME : Add watchdog */
+       system_set_default_power_state();
+       clock_config(SELECTED_FREQ);
+       set_pins(common_pins);
+       gpio_on();
+       /* System tick timer MUST be configured and running in order to use the sleeping
+        * functions */
+       systick_timer_on(1); /* 1ms */
+       systick_start();
+}
+
+/* Define our fault handler. This one is not mandatory, the dummy fault handler
+ * will be used when it's not overridden here.
+ * Note : The default one does a simple infinite loop. If the watchdog is deactivated
+ * the system will hang.
+ */
+void fault_info(const char* name, uint32_t len)
+{
+       debug_uprintf(name);
+       while (1);
+}
+
+
+/***************************************************************************** */
+/* PMU unit */
+int save_gp_retain(uint32_t* values, uint8_t nb, uint8_t offset)
+{
+       struct lpc_pm_unit* pmu = LPC_PMU;
+       int i = 0;
+
+       if ((offset + nb) > 4) {
+               return -EINVAL;
+       }
+       for (i = 0; i < nb; i++) {
+               pmu->gp_data[offset + i] = values[i];
+       }
+       return 0;
+}
+
+int read_gp_retain(uint32_t* values, uint8_t nb, uint8_t offset)
+{
+       struct lpc_pm_unit* pmu = LPC_PMU;
+       int i = 0;
+
+       if ((offset + nb) > 4) {
+               return -EINVAL;
+       }
+       for (i = 0; i < nb; i++) {
+               values[i] = pmu->gp_data[offset + i];
+       }
+       return 0;
+}
+
+/***************************************************************************** */
+/* BMS */
+
+#define BMS_BQ769X006_I2C_ADDR  0x30
+#define BMS_NB_CELLS 5
+#define BMS_NB_TH 1
+struct bq769x0_bms_conf bms = {
+       .addr = BMS_BQ769X006_I2C_ADDR,
+       .bus_num = I2C0,
+       .crc_check = 0, /* BQ769X006 does not have CRC */
+       .nb_cells = BMS_NB_CELLS,
+       .nb_thermistors = BMS_NB_TH,
+};
+
+void bms_config(void)
+{
+       int ret = 0;
+       uint16_t uvlo16 = 0, ovp16 = 0;
+       uint8_t uvlo = 0, ovp = 0;
+       uint8_t old_status = 0;
+       uint16_t vth = 0;
+       uint16_t vcells[BMS_NB_CELLS] = {0};
+
+       ret = bq769x0_bms_config(&bms, &old_status);
+       if (ret != 0) {
+               debug_uprintf("Battery Management System config error: %d, %d\n", bms.probe_ok, ret);
+               return;
+       } else {
+               debug_uprintf("Battery Management System UP !\n");
+               debug_uprintf(" old status: 0x%02x\n", old_status);
+               debug_uprintf(" ADC gain: %d (%d µV), off: %d\n",
+                                                       bms.adc_gain, bms.adc_gain_uV, bms.adc_offset);
+       }
+       msleep(100);
+       ret = bq769x0_bms_get_and_erase_status(&bms, &old_status);
+       if (ret != 0) {
+               debug_uprintf("Battery Management System get status error: %d\n", ret);
+               return;
+       } else {
+               debug_uprintf(" new status: 0x%02x\n", old_status);
+       }
+
+       /* Configure battery limits :
+        * - Cell uvlo : 2600mV
+        * - Cell ovp : 3650mV
+        * - scd is 22mV (0x00) : 22mV / 5mOhms = 4.4 Amps
+        * - ocd is 14mV (0x02) : 14mV / 5mOhms = 2.8 Amps
+        */
+       uvlo16 = ((2600 - bms.adc_offset) * 1000) / bms.adc_gain_uV;
+       uvlo = ((uvlo16 >> 4) & 0xFF);
+       ovp16 = ((3650 - bms.adc_offset) * 1000) / bms.adc_gain_uV;
+       ovp = ((ovp16 >> 4) & 0xFF);
+       ret = bq769x0_bms_set_ranges(&bms, uvlo, ovp, 0x02, 0);
+       if (ret != 0) {
+               debug_uprintf("BMS limit configuration error: %d (%d)!\n", ret, bms.probe_ok);
+       } else {
+               debug_uprintf("Limits set. \n UVLO: 2.6V (0x%02x), OVP: 3.65V (0x%02x)\n",      uvlo, ovp);
+               debug_uprintf(" SCD: 4.4A (22mV), OCD: 2.8A (14mV)\n");
+       }
+
+       /* Display cells voltage */
+       ret = bq769x0_bms_read_adc(&bms, vcells, ALL_CELLS, BMS_NB_CELLS);
+       if (ret != 0) {
+               debug_uprintf("BMS ADC read error for VCELLS: %d (%d)!\n", ret, bms.probe_ok);
+       } else {
+               int i = 0;
+               uint32_t vbatt = 0, v = 0;
+               for (i = 0; i < BMS_NB_CELLS; i++) {
+                       v = ((vcells[i] * bms.adc_gain_uV) / 1000) + bms.adc_offset;
+                       vbatt += v;
+                       debug_uprintf(" Cell %d voltage : %d (mV)\n", i, v);
+               }
+               debug_uprintf("Battery voltage : %d (mV)\n", vbatt);
+       }
+
+       /* Display thermistor voltage */
+       ret = bq769x0_bms_read_adc(&bms, &vth, THERMISTORS, BMS_NB_TH);
+       if (ret != 0) {
+               debug_uprintf("BMS ADC read error for VTH: %d (%d)!\n", ret, bms.probe_ok);
+       } else {
+               uint32_t v = ((vth * 382) / 1000) + bms.adc_offset;
+               uint32_t r = (v * 10000) / (3300 - v);
+               debug_uprintf("Thermistor voltage : %d (mV), Rts: %d (Ohms)\n", v, r);
+       }
+}
+
+
+/* Battery allert is set whenever there"s a new CC count to read, or on any battery error */
+volatile uint8_t batt_event_detected = 0;
+void batt_alert(uint32_t gpio)
+{
+       batt_event_detected = 1;
+}
+
+static uint8_t charge_state = 0;
+static uint8_t discharge_state = 0;
+
+/* Count the number of CC cycles since system start.
+ * Wraps after 34 years with CC events every 250ms.
+ */
+static uint32_t cycles = 0;
+
+#define PKT_START  '#'
+struct bms_data {
+       /* Header */
+       uint8_t start;
+       uint8_t status; /* Actual status */
+       uint8_t charge;
+       uint8_t checksum; /* Checksum */
+
+       /* Battery power stored in "uW * s" (microWatt second) */
+       int64_t batt_power;
+       int64_t batt_power_max;
+       int64_t cycle_power;
+
+       /* Number of cycles since system startup */
+       uint32_t cycles;
+
+       /* Current in mA */
+       int32_t amps;
+
+       /* Vcells in mV */
+       uint16_t vcells[4];
+} __attribute__ ((__packed__));
+
+/* Battery power stored in "uW * s" (microWatt second) */
+static int64_t batt_used = 0;
+static int64_t batt_charge = 0;
+static int64_t batt_power = 0;
+static int64_t batt_power_max = 0;
+       
+/*
+ * Power computation is the following :
+ * E = P * t = U * I * t = U * U / R * t
+ *   E is in Ws (Watt second)
+ *   t = 250ms = 1/4s  (CC integration time)
+ *   R = 5 * 10^-3 Ohms
+ *   U is measured in uV (10^-6), N is the value when U = N*10^-6 Volts
+ * E = N * N * 10^-12 / ( 5 * 10^-3 * 4)
+ * E = N * N * 10^-9 / 20
+ * E = N * N * 10^-8 / 2 = N * N * 10 * 10^-9 / 2 
+ * E = N * N * (10 / 2) * 10^-9
+ * E = N * N * 5 * 10^-9
+ *   CC readings have a 8.44uV LSB, so N * N * 5 = CC * 8.44 * CC * 8.44 * 5 = CC * CC * 356
+ * E = (CC * CC * 356 / 1000) * 10^-6
+ */
+int periodic_check(void)
+{
+       struct bms_data bdata = {0};
+       uint16_t vcells[BMS_NB_CELLS] = {0};
+       uint8_t charge = 1, discharge = 1;
+       uint8_t status = 0;
+       int ret = 0;
+
+       /* Read global status */
+       ret = bq769x0_bms_get_and_erase_status(&bms, &status);
+       if (ret != 0) {
+               debug_uprintf("Battery Management System get status error: %d\n", ret);
+               return -1;
+       }
+       bdata.status = status;
+
+       if (status & 0x3F) {
+               gpio_clear(led);
+               /* Perform necessary actions ! */
+               if (SHORT_CIRCUIT_DISCHARGE(status)) {
+                       charge = 0, discharge = 0;
+                       /* FIXME : Disable for a longer time */
+               } else if (OVER_CURRENT_DISCHARGE(status)) {
+                       charge = 0, discharge = 0;
+               } else if (OVER_VOLTAGE(status)) {
+                       charge = 0;
+               } else if (UNDER_VOLTAGE(status)) {
+                       discharge = 0;
+               }
+               debug_uprintf(" BMS status: 0x%02x\n", status);
+       } else {
+               gpio_set(led);
+       }
+
+       /* Update battery state based on errors and old state */
+       if ((charge != charge_state) || (discharge != discharge_state)) {
+               msleep(2);
+               ret = bq769x0_bms_change_state(&bms, charge, discharge);
+               if (ret != 0) {
+                       debug_uprintf(" BMS Change state error ch:%d, dis:%d\n", charge, discharge);
+               } else {
+                       debug_uprintf(" BMS state changed : charge: %d, discharge: %d\n", charge, discharge);
+                       charge_state = charge;
+                       discharge_state = discharge;
+               }
+       }
+       
+       /* Display Coulomb counter */
+       if (CC_READY(status)) {
+               int16_t cc = 0;
+               msleep(2);
+               ret = bq769x0_bms_read_adc(&bms, (uint16_t*)&cc, CC_COUNT, 1);
+               if (ret != 0) {
+                       debug_uprintf("BMS ADC read error for CC_COUNT: %d (%d)!\n", ret, bms.probe_ok);
+               } else {
+                       int32_t tmp = ((cc * 844) / 100); /* LSB is 8.44uV */
+                       int32_t amps = (tmp / 5); /* I = U/R = tmp * 10^-6 / (5 * 10^-3) = (tmp / 5) * 10^-3 A = (tmp / 5) mA */
+                       int64_t power = ((cc * cc * 356) / 1000); /* See on top of function for explanation */
+
+                       if (amps > 2000) {
+                               /* Do not charge at over 2 Amp */
+                               msleep(2);
+                               charge_state = 0;
+                               ret = bq769x0_bms_change_state(&bms, charge_state, discharge_state);
+                               debug_uprintf("BMS detected fast charge over 2Amp, charge turned OFF\n");
+                       }
+
+                       if (cc < 0) {
+                               batt_used += power;
+                               batt_power -= power;
+                       } else {
+                               batt_charge += power;
+                               batt_power += power;
+                       }
+                       if (batt_power < 0) {
+                               /* batt_power reflect remaining battery juce, so getting lower means we are
+                                * getting more out of the battery, but there cannot be less than 0 */
+                               batt_power = 0;
+                               debug_uprintf("Battery has more than that !\n");
+                       } else if ((uint32_t)(batt_power >> 10) > (uint32_t)(batt_power_max >> 10)) {
+                               /* Update max battery power */
+                               batt_power_max = batt_power;
+                               /* Save max battery charge to the last two retain registers */
+                               ret = save_gp_retain((uint32_t*)&batt_power_max, 2, 2);
+                               debug_uprintf("Battery max power updated: %d (ret: %d)\n", batt_power_max, ret);
+                       } else {
+                               debug_uprintf("Within range ...\n");
+                       }
+                       debug_uprintf("CC reading: %d : %d uV or %d uWs\n", cc, tmp, power);
+                       debug_uprintf("Current: %d mA\n", amps);
+                       debug_uprintf("Battery remaining power : %d/%d mWs\n",
+                                                               (batt_power >> 10), (batt_power_max >> 10)); /* divide by 1024 */
+
+                       /* Count 1 cycle for each battery event */
+                       cycles++;
+                       bdata.cycles = cycles;
+                       bdata.amps = amps;
+                       bdata.cycle_power = power;
+                       bdata.batt_power = batt_power;
+                       bdata.batt_power_max = batt_power_max;
+               }
+               /* Save current battery charge status to the first two retain registers */
+               save_gp_retain((uint32_t*)&batt_power, 2, 0);
+       }
+
+       /* Display cells voltage */
+       msleep(2);
+       ret = bq769x0_bms_read_adc(&bms, vcells, ALL_CELLS, BMS_NB_CELLS);
+       if (ret != 0) {
+               debug_uprintf("BMS ADC read error for VCELLS: %d (%d)!\n", ret, bms.probe_ok);
+       } else {
+               int i = 0;
+               uint32_t vbatt = 0, v = 0;
+               uint8_t cnt = 0;
+               for (i = 0; i < BMS_NB_CELLS; i++) {
+                       /* Real number of cells is 4, so skip cell number 3 */
+                       if (i == 3) { continue; }
+                       v = ((vcells[i] * bms.adc_gain_uV) / 1000) + bms.adc_offset;
+                       vbatt += v;
+                       debug_uprintf(" Cell %d : %d (mV)\n", i, v);
+                       bdata.vcells[cnt++] = v;
+               }
+               debug_uprintf("Battery voltage : %d (mV)\n", vbatt);
+       }
+
+       if (verbose >= 1) {
+               int i = 0;
+               uint8_t* data = (uint8_t*)(&bdata);
+               uint8_t cksum = 0;
+
+               bdata.start = PKT_START;
+               bdata.charge = (charge_state | (discharge_state << 4));
+               for (i = 0; i < sizeof(struct bms_data); i++) {
+                       cksum += data[i];
+               }
+
+               debug_uprintf("Tick : %d\n", systick_get_tick_count());
+
+               bdata.checksum = (uint8_t)(256 - cksum);
+               serial_write(UART1, (char*)(&bdata), sizeof(struct bms_data));
+       }
+
+       return 0;
+}
+
+volatile uint8_t cmd = 0;
+void cmd_rx(uint8_t c)
+{
+       cmd = c;
+}
+
+int handle_cmd(void)
+{
+       int ret = 0;
+
+       switch (cmd) {
+               /* Charge control */
+               case 'C': /* Charge Enable */
+                       ret = bq769x0_bms_change_state(&bms, 1, discharge_state); /* charge on, discharge on */
+                       if (ret != 0) {
+                               debug_uprintf(" BMS CMD Enable charge error ch:%d, dis:%d\n", charge_state, discharge_state);
+                       } else {
+                               debug_uprintf("BMS charge Enabled\n");
+                               charge_state = 1;
+                       }
+                       break;
+               case 'S': /* Charge Disable (Stop) */
+                       ret = bq769x0_bms_change_state(&bms, 0, discharge_state); /* charge off, discharge on*/
+                       if (ret != 0) {
+                               debug_uprintf(" BMS CMD Stop charge error ch:%d, dis:%d\n", charge_state, discharge_state);
+                       } else {
+                               debug_uprintf("BMS charge Disabled\n");
+                               charge_state = 0;
+                       }
+                       break;
+
+               /* Verbosity control : turn verbosity on or off to save power */
+               case 'L': /* Low power */
+                       debug_uprintf("Setting verbosity to 0\n");
+                       verbose = 0;
+                       break;
+               case 'v':
+                       debug_uprintf("Setting verbosity to 1\n");
+                       verbose = 1;
+                       break;
+               case 'V':
+                       verbose = 2;
+                       debug_uprintf("Verbosity set to 2\n");
+                       break;
+       }
+       return 0;
+}
+
+/***************************************************************************** */
+/* Use a countdown value to track activity and go to sleep state */
+#define PING_RESET_VAL  128
+static uint8_t ping = PING_RESET_VAL;
+
+int enter_sleep_mode(void)
+{
+       /* FIXME */
+       return 0;
+}
+
+/***************************************************************************** */
+int main(void)
+{
+       system_init();
+       uart_on(UART0, 115200, cmd_rx);
+       uart_on(UART1, 115200, cmd_rx);
+
+       debug_uprintf("Sarting\n");
+       /* I2C config */
+       i2c_on(I2C0, I2C_CLK_100KHz, I2C_MASTER);
+       msleep(10);
+       bms_config();
+
+       debug_uprintf("I2C config done\n");
+
+       /* GPIO configuration */
+       config_gpio(&led, 0, GPIO_DIR_OUT, 0); /* FIXME : Starting with led ON for debug, set to 1 (OFF) for normal case */
+       config_gpio(&alert, 0, GPIO_DIR_IN, 0);
+
+       debug_uprintf("GPIO config done\n");
+
+       /* Activate on both edges */
+       set_gpio_callback(batt_alert, &alert, EDGES_BOTH);
+       debug_uprintf("Button config done\n");
+
+       /* Read old battery state values */
+       read_gp_retain((uint32_t*)&batt_power_max, 2, 2);
+       read_gp_retain((uint32_t*)&batt_power, 2, 0);
+       debug_uprintf("Batt power from retain registers: max: %d, actual: %d\n",
+                                               batt_power_max >> 10, batt_power >> 10); /* divide by 1024 */
+
+       /* Perform a first periodic check to make sure we did not miss an alert,
+        * but wait for the 250ms delay requested for BMS stability as requested in section
+        * 10.3 "Feature description" (10.3.1.1.2) of BQ76920 datasheet (SLUSBK2H)
+        */
+       msleep(400);
+       periodic_check();
+
+       /* Lower the verbosity */
+       verbose = 0;
+
+       while (1) {
+
+               if (batt_event_detected == 1) {
+                       periodic_check();
+                       batt_event_detected = 0;
+                       /* Decrease the ping counter */
+                       if (ping > 0) {
+                               ping--;
+                       }
+               }
+
+               if (cmd != 0) {
+                       handle_cmd();
+                       ping = PING_RESET_VAL;
+                       cmd = 0;
+               }
+
+               /* Ping counter reached 0, go to sleep mode */
+               if (ping == 0) {
+                       verbose = 0;
+                       enter_sleep_mode();
+               }
+       }
+       return 0;
+}
+
+
+
+