diff --git a/libraries/ms-common/inc/crc15.h b/libraries/ms-common/inc/crc15.h new file mode 100644 index 000000000..d9011687a --- /dev/null +++ b/libraries/ms-common/inc/crc15.h @@ -0,0 +1,8 @@ +#pragma once +// crc15 implementation for the LTC6811 +#include +#include + +void crc15_init_table(void); + +uint16_t crc15_calculate(uint8_t *data, size_t len); diff --git a/libraries/ms-common/inc/notify.h b/libraries/ms-common/inc/notify.h index 2db0ff657..ff7e28fb7 100644 --- a/libraries/ms-common/inc/notify.h +++ b/libraries/ms-common/inc/notify.h @@ -28,7 +28,7 @@ typedef enum { NUM_PUB_TOPICS, } Topic; -// Gets highest priority (highest value) event available, and clears it +// Gets highest priority (highest value) event available, and clears it // Returns STATUS_CODE_OK if all events processed, STATUS_CODE_INCOMPLETE if any remaining // Notification value should be processed until all events are cleared StatusCode event_from_notification(uint32_t *notification, Event *event); diff --git a/libraries/ms-common/src/crc15.c b/libraries/ms-common/src/crc15.c new file mode 100644 index 000000000..00794a5cc --- /dev/null +++ b/libraries/ms-common/src/crc15.c @@ -0,0 +1,37 @@ +#include "crc15.h" + +// x^{15} + x^{14} + x^{10} + x^{8} + x^{7} + x^{4} + x^{3} + x^{0} +// so divisor is: 0b1100010110011001 (0xC599) +// 0xC599 - (2^15) == 0x4599 +#define CRC_POLYNOMIAL 0x4599 + +static uint16_t s_crc15_table[256]; + +void crc15_init_table(void) { + for (uint32_t i = 0; i < 256; ++i) { + uint32_t remainder = i << 7; + for (uint8_t bit = 8; bit > 0; --bit) { + if (remainder & 0x4000) { + // check MSB + remainder = (remainder << 1); + remainder = (remainder ^ CRC_POLYNOMIAL); + } else { + remainder = (remainder << 1); + } + } + + s_crc15_table[i] = remainder & 0xFFFF; + } +} + +uint16_t crc15_calculate(uint8_t *data, size_t len) { + // CRC should be initialized to 16 (see datasheet p.44) + uint16_t remainder = 16; + + for (size_t i = 0; i < len; i++) { + uint16_t addr = ((remainder >> 7) ^ data[i]) & 0xFF; + remainder = (remainder << 8) ^ s_crc15_table[addr]; + } + + return remainder << 1; +} diff --git a/libraries/ms-drivers/inc/ads1259.h b/libraries/ms-drivers/inc/ads1259.h new file mode 100644 index 000000000..cd9f0584e --- /dev/null +++ b/libraries/ms-drivers/inc/ads1259.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include + +#include "ads1259_def.h" +#include "gpio.h" +#include "spi.h" + +typedef enum Ads1259StatusCode { + ADS1259_STATUS_CODE_OK = 0, + ADS1259_STATUS_CODE_OUT_OF_RANGE, + ADS1259_STATUS_CODE_CHECKSUM_FAULT, + ADS1259_STATUS_CODE_DATA_NOT_READY, + NUM_ADS1259_STATUS_CODE, +} Ads1259StatusCode; + +typedef void (*Ads1259ErrorHandlerCb)(Ads1259StatusCode code, void *context); + +typedef struct Ads1259RxData { + uint8_t MSB; + uint8_t MID; + uint8_t LSB; + uint8_t CHK_SUM; +} Ads1259RxData; + +typedef union Ads1259ConversionData { + struct { + uint8_t LSB; + uint8_t MID; + uint8_t MSB; + }; + uint32_t raw; +} Ads1259ConversionData; + +// Initialize Ads1259Settings with SPI settings and error callback function +typedef struct Ads1259Settings { + SpiPort spi_port; + uint32_t spi_baudrate; + GpioAddress mosi; + GpioAddress miso; + GpioAddress sclk; + GpioAddress cs; + Ads1259ErrorHandlerCb handler; + void *error_context; +} Ads1259Settings; + +// Static instance of Ads1259Storage must be declared +// ads1259_get_data() reads 24-bit conversion data into 'reading' +typedef struct Ads1259Storage { + Ads1259RxData rx_data; + SpiPort spi_port; + Ads1259ConversionData conv_data; + double reading; + Ads1259ErrorHandlerCb handler; + void *error_context; +} Ads1259Storage; + +// Initializes ads1259 - soft-timers and spi must be initialized +StatusCode ads1259_init(Ads1259Storage *storage, Ads1259Settings *settings); + +// Gets reading via single conversion mode +StatusCode ads1259_get_conversion_data(Ads1259Storage *storage); diff --git a/libraries/ms-drivers/inc/ads1259_def.h b/libraries/ms-drivers/inc/ads1259_def.h new file mode 100644 index 000000000..fe9eab78d --- /dev/null +++ b/libraries/ms-drivers/inc/ads1259_def.h @@ -0,0 +1,85 @@ +#pragma once + +// Voltage reference value +#define EXTERNAL_VREF_V 2.5 + +// ADS1259 Configuration and control commands + +#define ADS1259_WAKEUP 0x02 // Wake up from sleep mode +#define ADS1259_SLEEP 0x04 // Begin Sleep Mode +#define ADS1259_RESET 0x06 // Reset to power-up values +#define ADS1259_START_CONV 0x08 // Start Conversion +#define ADS1259_STOP_CONV 0x0A // Stops all conversions after one in progress is complete +#define ADS1259_START_READ_DATA_CONTINOUS 0x10 // enables the Read Data Continuous mode +#define ADS1259_STOP_READ_DATA_CONTINUOUS 0x11 // cancels the Read Data Continuous mode +#define ADS1259_READ_DATA_BY_OPCODE 0x12 // read the conversion result +#define ADS1259_OFFSET_CALIBRATION 0x18 // performs an offset calibration +#define ADS1259_GAIN_CALIBRATION 0x19 // performs a gain calibration + +// ADS1259 READ/WRITE REGISTER COMMANDS +// Each command requires 2 bytes: +// command opcode and register address +// number of registers to read +#define ADS1259_READ_REGISTER 0x20 +#define ADS1259_WRITE_REGISTER 0x40 + +// Samples per second on ADS1259 +typedef enum Ads1259DataRate { + ADS1259_DATA_RATE_10 = 0, + ADS1259_DATA_RATE_17, + ADS1259_DATA_RATE_50, + ADS1259_DATA_RATE_60, + ADS1259_DATA_RATE_400, + ADS1259_DATA_RATE_1200, + ADS1259_DATA_RATE_3600, + ADS1259_DATA_RATE_14400, + NUM_ADS1259_DATA_RATE, +} Ads1259DataRate; + +// REGISTER ADDRESSES +#define ADS1259_ADDRESS_CONFIG0 0x00 +#define ADS1259_ADDRESS_CONFIG1 0x01 +#define ADS1259_ADDRESS_CONFIG2 0x02 +#define ADS1259_ADDRESS_OFC0 0x03 +#define ADS1259_ADDRESS_OFC1 0x04 +#define ADS1259_ADDRESS_OFC2 0x05 +#define ADS1259_ADDRESS_FSC0 0x06 +#define ADS1259_ADDRESS_FSC1 0x07 +#define ADS1259_ADDRESS_FSC2 0x08 +#define NUM_ADS1259_REGISTERS 0x09 + +// ADS1259 Register Configurations - LSB to MSB +// These values can be changed based on needed configurations + +// CONFIG0 +#define ADS1259_SPI_TIMEOUT_ENABLE 0x01 // Enable SPI Timeout +#define ADS1259_INTERNAL_REF_BIAS_ENABLE 0x04 // Enable internal ref bias + +// CONFIG1 +#define ADS1259_CONVERSION_DELAY_MS 0x0 // Conversion delay not used -> if needed see datasheet +#define ADS1259_VREF_EXTERNAL 0x08 // Enable external voltage reference +#define ADS1259_DIGITAL_FILTER_2 0x10 // Enable SINC2 digital filter +#define ADS1259_CHECK_SUM_ENABLE 0x40 // Enable check sum byte +#define ADS1259_OUT_OF_RANGE_FLAG_ENABLE 0x80 // Enable out of range flag + +// CONFIG2 +#define ADS1259_SYNCOUT_ENABLE 0x20 // Enable Syncout clock +#define ADS1259_CONVERSION_CONTROL_MODE_PULSE 0x10 // Set Conversion mode to pulse +#define ADS1259_DATA_RATE_SPS ADS1259_DATA_RATE_60 + +// Offset used in Checksum calculation +#define ADS1259_CHECKSUM_OFFSET 0x9B + +typedef enum Ads1259RxByte { + ADS1259_MSB = 0, + ADS1259_MID, + ADS1259_LSB, + ADS1259_CHK_SUM, + NUM_ADS_RX_BYTES, +} Ads1259RxByte; + +#define NUM_CONFIG_REGISTERS 3 +#define NUM_REGISTER_WRITE_COMM 5 +#define CHK_SUM_FLAG_BIT 0x80 +#define RX_NEG_VOLTAGE_BIT 0x800000 +#define RX_MAX_VALUE 0x1000000 diff --git a/libraries/ms-drivers/inc/ltc6811.h b/libraries/ms-drivers/inc/ltc6811.h new file mode 100644 index 000000000..26ce022d2 --- /dev/null +++ b/libraries/ms-drivers/inc/ltc6811.h @@ -0,0 +1,226 @@ +#pragma once +#include + +// used internally by the LTC AFE driver +#define LTC6811_CELLS_IN_REG 3 +#define LTC6811_GPIOS_IN_REG 3 + +// used for the external mux (ADG731) connected to the AFE +#define AUX_ADG731_NUM_PINS 32 + +// Size of command code + PEC +#define LTC6811_CMD_SIZE 4 + +// 3 bytes are required to send 24 clock cycles with our SPI driver for the STCOMM command +#define LTC6811_NUM_COMM_REG_BYTES 3 + +typedef enum { + LTC_AFE_REGISTER_CONFIG = 0, + LTC_AFE_REGISTER_CELL_VOLTAGE_A, + LTC_AFE_REGISTER_CELL_VOLTAGE_B, + LTC_AFE_REGISTER_CELL_VOLTAGE_C, + LTC_AFE_REGISTER_CELL_VOLTAGE_D, + LTC_AFE_REGISTER_AUX_A, + LTC_AFE_REGISTER_AUX_B, + LTC_AFE_REGISTER_STATUS_A, + LTC_AFE_REGISTER_STATUS_B, + LTC_AFE_REGISTER_READ_COMM, + LTC_AFE_REGISTER_START_COMM, + NUM_LTC_AFE_REGISTERS +} LtcAfeRegister; + +typedef enum { + LTC_AFE_VOLTAGE_REGISTER_A = 0, + LTC_AFE_VOLTAGE_REGISTER_B, + LTC_AFE_VOLTAGE_REGISTER_C, + LTC_AFE_VOLTAGE_REGISTER_D, + NUM_LTC_AFE_VOLTAGE_REGISTERS +} LtcAfeVoltageRegister; + +typedef enum { + LTC_AFE_DISCHARGE_TIMEOUT_DISABLED = 0, + LTC_AFE_DISCHARGE_TIMEOUT_30_S, + LTC_AFE_DISCHARGE_TIMEOUT_1_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_2_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_3_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_4_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_5_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_10_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_15_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_20_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_30_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_40_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_60_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_75_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_90_MIN, + LTC_AFE_DISCHARGE_TIMEOUT_120_MIN +} LtcAfeDischargeTimeout; + +// SPI Packets +typedef struct { + uint8_t adcopt : 1; + uint8_t swtrd : 1; + uint8_t refon : 1; + + uint8_t gpio : 5; // GPIO pin control + + uint32_t undervoltage : 12; // Undervoltage Comparison Voltage + uint32_t overvoltage : 12; // Overvoltage Comparison Voltage + + uint16_t discharge_bitset : 12; + uint8_t discharge_timeout : 4; +} _PACKED LtcAfeConfigRegisterData; +static_assert(sizeof(LtcAfeConfigRegisterData) == 6, "LtcAfeConfigRegisterData must be 6 bytes"); + +// COMM Register, refer to LTC6803 datasheet page 31, Table 15 +typedef struct { + uint8_t icom0 : 4; + uint8_t d0 : 8; + uint8_t fcom0 : 4; + + uint8_t icom1 : 4; + uint8_t d1 : 8; + uint8_t fcom1 : 4; + + uint8_t icom2 : 4; + uint8_t d2 : 8; + uint8_t fcom2 : 4; +} _PACKED LtcAfeCommRegisterData; +static_assert(sizeof(LtcAfeCommRegisterData) == 6, "LtcAfeCommRegisterData must be 6 bytes"); + +// CFGR packet +typedef struct { + LtcAfeConfigRegisterData reg; + + uint16_t pec; +} _PACKED LtcAfeWriteDeviceConfigPacket; + +// WRCOMM + mux pin +typedef struct { + uint8_t wrcomm[LTC6811_CMD_SIZE]; + LtcAfeCommRegisterData reg; + uint8_t pec; +} _PACKED LtcAfeWriteCommRegPacket; + +// STMCOMM + clock cycles +typedef struct { + uint8_t stcomm[LTC6811_CMD_SIZE]; + uint8_t clk[LTC6811_NUM_COMM_REG_BYTES]; +} _PACKED LtcAfeSendCommRegPacket; + +// WRCFG + all slave registers +typedef struct { + uint8_t wrcfg[LTC6811_CMD_SIZE]; + + // devices are ordered with the last slave first + LtcAfeWriteDeviceConfigPacket devices[LTC_AFE_MAX_CELLS_PER_DEVICE]; +} _PACKED LtcAfeWriteConfigPacket; +#define SIZEOF_LTC_AFE_WRITE_CONFIG_PACKET(devices) \ + (LTC6811_CMD_SIZE + (devices) * sizeof(LtcAfeWriteConfigPacket)) + +typedef union { + uint16_t voltages[3]; + + uint8_t values[6]; +} LtcAfeRegisterGroup; +static_assert(sizeof(LtcAfeRegisterGroup) == 6, "LtcAfeRegisterGroup must be 6 bytes"); + +typedef struct { + LtcAfeRegisterGroup reg; + + uint16_t pec; +} _PACKED LtcAfeVoltageRegisterGroup; +static_assert(sizeof(LtcAfeVoltageRegisterGroup) == 8, + "LtcAfeVoltageRegisterGroup must be 8 bytes"); + +typedef struct { + LtcAfeRegisterGroup reg; + + uint16_t pec; +} _PACKED LtcAfeAuxRegisterGroupPacket; +static_assert(sizeof(LtcAfeAuxRegisterGroupPacket) == 8, + "LtcAfeAuxRegisterGroupPacket must be 8 bytes"); + +// command codes +// see Table 38 (p.59) +#define LTC6811_WRCFG_RESERVED (1 << 0) + +#define LTC6811_RDCFG_RESERVED (1 << 1) + +#define LTC6811_RDCVA_RESERVED (1 << 2) + +#define LTC6811_RDCVB_RESERVED (1 << 2) | (1 << 1) + +#define LTC6811_RDCVC_RESERVED (1 << 3) + +#define LTC6811_RDCVD_RESERVED (1 << 3) | (1 << 1) + +#define LTC6811_RDAUXA_RESERVED ((1 << 3) | (1 << 2)) + +#define LTC6811_RDAUXB_RESERVED ((1 << 3) | (1 << 2)) | (1 << 1) + +#define LTC6811_RDSTATA_RESERVED (1 << 4) + +#define LTC6811_RDSTATB_RESERVED (1 << 4) | (1 << 1) + +#define LTC6811_ADCV_RESERVED ((1 << 9) | (1 << 6) | (1 << 5)) + +#define LTC6811_ADCOW_RESERVED ((1 << 3) | (1 << 5) | (1 << 9)) + +#define LTC6811_CVST_RESERVED ((1 << 0) | (1 << 1) | (1 << 2) | (1 << 9)) + +#define LTC6811_ADAX_RESERVED (1 << 10) | (1 << 6) | (1 << 5) + +#define LTC6811_CLRCELL_RESERVED (1 << 0) | (1 << 4) | (1 << 8) | (1 << 9) | (1 << 10) + +#define LTC6811_CLRAUX_RESERVED (1 << 1) | (1 << 4) | (1 << 8) | (1 << 9) | (1 << 10) + +#define LTC6811_CLRSTAT_RESERVED (1 << 0) | (1 << 1) | (1 << 4) | (1 << 8) | (1 << 9) | (1 << 10) + +#define LTC6811_PLADC_RESERVED (1 << 2) | (1 << 4) | (1 << 8) | (1 << 9) | (1 << 10) + +#define LTC6811_DIAGNC_RESERVED (1 << 0) | (1 << 2) | (1 << 4) | (1 << 8) | (1 << 9) | (1 << 10) + +#define LTC6811_WRCOMM_RESERVED (1 << 0) | (1 << 5) | (1 << 8) | (1 << 9) | (1 << 10) + +#define LTC6811_RDCOMM_RESERVED (1 << 1) | (1 << 5) | (1 << 8) | (1 << 9) | (1 << 10) + +#define LTC6811_STCOMM_RESERVED (1 << 0) | (1 << 1) | (1 << 5) | (1 << 8) | (1 << 9) | (1 << 10) + +// command bits +// see Table 40 (p. 62) +#define LTC6811_GPIO1_PD_ON (0 << 3) +#define LTC6811_GPIO1_PD_OFF (1 << 3) +#define LTC6811_GPIO2_PD_ON (0 << 4) +#define LTC6811_GPIO2_PD_OFF (1 << 4) +#define LTC6811_GPIO3_PD_ON (0 << 5) +#define LTC6811_GPIO3_PD_OFF (1 << 5) +#define LTC6811_GPIO4_PD_ON (0 << 6) +#define LTC6811_GPIO4_PD_OFF (1 << 6) +#define LTC6811_GPIO5_PD_ON (0 << 7) +#define LTC6811_GPIO5_PD_OFF (1 << 7) + +#define LTC6811_CNVT_CELL_ALL 0x00 +#define LTC6811_CNVT_CELL_1_7 0x01 +#define LTC6811_CNVT_CELL_2_8 0x02 +#define LTC6811_CNVT_CELL_3_9 0x03 +#define LTC6811_CNVT_CELL_4_10 0x04 +#define LTC6811_CNVT_CELL_5_11 0x05 +#define LTC6811_CNVT_CELL_6_12 0x06 + +#define LTC6811_ADCV_DISCHARGE_NOT_PERMITTED (0 << 4) +#define LTC6811_ADCV_DISCHARGE_PERMITTED (1 << 4) + +#define LTC6811_ADCOPT (1 << 0) + +#define LTC6811_SWTRD (1 << 1) + +#define LTC6811_ADAX_GPIO1 0x01 +#define LTC6811_ADAX_MODE_FAST (0 << 8) | (1 << 7) + +#define LTC6811_ICOM_CSBM_LOW (1 << 3) +#define LTC6811_ICOM_CSBM_HIGH (1 << 3) | (1 << 0) +#define LTC6811_ICOM_NO_TRANSMIT (1 << 3) | (1 << 2) | (1 << 1) | (1 << 0) + +#define LTC6811_FCOM_CSBM_LOW (0 << 0) +#define LTC6811_FCOM_CSBM_HIGH (1 << 3) | (1 << 0) diff --git a/libraries/ms-drivers/inc/ltc_afe.h b/libraries/ms-drivers/inc/ltc_afe.h new file mode 100644 index 000000000..2c634505a --- /dev/null +++ b/libraries/ms-drivers/inc/ltc_afe.h @@ -0,0 +1,100 @@ +#pragma once +// Driver for LTC6811 AFE chip + +// TODO(SOFT-9): Need to update GPIO/ADC part + +// Requires GPIO, Interrupts, Soft Timers, and Event Queue to be initialized + +// Note that all units are in 100uV. + +// This module supports AFEs with >=12 cells using the |cell/aux_bitset|. +// Note that due to the long conversion delays required, we use an FSM to return control to the +// application. +#include +#include +#include +#include + +#include "fsm.h" +#include "gpio.h" +#include "spi.h" +#include "status.h" + +// This is an arbitrary limitation, can be increased/decreased if needed +#define LTC_AFE_MAX_DEVICES 5 +// This is a device limitation +#define LTC_AFE_MAX_CELLS_PER_DEVICE 12 +#define LTC_AFE_MAX_CELLS (LTC_AFE_MAX_DEVICES * LTC_AFE_MAX_CELLS_PER_DEVICE) +#define LTC_AFE_MAX_THERMISTORS LTC_AFE_MAX_CELLS + +#if defined(__GNUC__) +#define _PACKED __attribute__((packed)) +#else +#define _PACKED +#endif + +// select the ADC mode (trade-off between speed or minimizing noise) +// see p.50 for conversion times and p.23 for noise +typedef enum { + LTC_AFE_ADC_MODE_27KHZ = 0, + LTC_AFE_ADC_MODE_7KHZ, + LTC_AFE_ADC_MODE_26HZ, + LTC_AFE_ADC_MODE_14KHZ, + LTC_AFE_ADC_MODE_3KHZ, + LTC_AFE_ADC_MODE_2KHZ, + NUM_LTC_AFE_ADC_MODES +} LtcAfeAdcMode; + +typedef struct LtcAfeBitset { + uint16_t cell_bitset; + uint16_t aux_bitset; +} LtcAfeBitset; + +typedef struct LtcAfeSettings { + GpioAddress cs; + GpioAddress mosi; + GpioAddress miso; + GpioAddress sclk; + + const SpiPort spi_port; + uint32_t spi_baudrate; + + LtcAfeAdcMode adc_mode; + + uint16_t cell_bitset[LTC_AFE_MAX_DEVICES]; + uint16_t aux_bitset[LTC_AFE_MAX_DEVICES]; + + size_t num_devices; + size_t num_cells; + size_t num_thermistors; + + void *result_context; +} LtcAfeSettings; + +typedef struct LtcAfeStorage { + Fsm fsm; + + // Only used for storage in the FSM so we store data for the correct cells + uint16_t aux_index; + uint16_t retry_count; + uint16_t device_cell; + uint16_t time_elapsed; + + uint16_t cell_voltages[LTC_AFE_MAX_CELLS]; + uint16_t aux_voltages[LTC_AFE_MAX_THERMISTORS]; + + uint16_t discharge_bitset[LTC_AFE_MAX_DEVICES]; + + uint16_t cell_result_lookup[LTC_AFE_MAX_CELLS]; + uint16_t aux_result_lookup[LTC_AFE_MAX_THERMISTORS]; + uint16_t discharge_cell_lookup[LTC_AFE_MAX_CELLS]; + + LtcAfeSettings settings; +} LtcAfeStorage; + +// Initialize the LTC6811. +// |settings.cell_bitset| and |settings.aux_bitset| should be an array of bitsets where bits 0 to 11 +// represent whether we should monitor the cell input for the given device. +// |settings.cell_result_cb| and |settings.aux_result_cb| will be called when the corresponding +// conversion is completed. +StatusCode ltc_afe_init(LtcAfeStorage *afe, const LtcAfeSettings *settings); diff --git a/libraries/ms-drivers/inc/ltc_afe_impl.h b/libraries/ms-drivers/inc/ltc_afe_impl.h new file mode 100644 index 000000000..71da1e27d --- /dev/null +++ b/libraries/ms-drivers/inc/ltc_afe_impl.h @@ -0,0 +1,31 @@ +#pragma once +// Helper functions for the LTC6811 +// +// This module is mostly exposed for the FSM. Do not use functions in this module directly. +// Requires SPI, soft timers to be initialized +// +// Assumes that: +// Requires GPIO, Interrupts and Soft Timers to be initialized +// +// Note that all units are in 100uV. +// +// This module supports AFEs with fewer than 12 cells using the |input_bitset|. +#include "ltc_afe.h" + +// Initialize the LTC6811. +// |settings.cell_bitset| and |settings.aux_bitset| should be an array of bitsets where bits 0 to 11 +// represent whether we should monitor the cell input for the given device. +StatusCode ltc_afe_impl_init(LtcAfeStorage *afe, const LtcAfeSettings *settings); + +// Triggers a conversion. Note that we need to wait for the conversions to complete before the +// readback will be valid. +StatusCode ltc_afe_impl_trigger_cell_conv(LtcAfeStorage *afe); +StatusCode ltc_afe_impl_trigger_aux_conv(LtcAfeStorage *afe, uint8_t device_cell); + +// Reads converted voltages from the AFE into the storage result arrays. +StatusCode ltc_afe_impl_read_cells(LtcAfeStorage *afe); +StatusCode ltc_afe_impl_read_aux(LtcAfeStorage *afe, uint8_t device_cell); + +// Mark cell for discharging (takes effect after config is re-written) +// |cell| should be [0, LTC_AFE_MAX_CELLS) +StatusCode ltc_afe_impl_toggle_cell_discharge(LtcAfeStorage *afe, uint16_t cell, bool discharge); diff --git a/libraries/ms-drivers/src/ads1259.c b/libraries/ms-drivers/src/ads1259.c new file mode 100644 index 000000000..beffb705c --- /dev/null +++ b/libraries/ms-drivers/src/ads1259.c @@ -0,0 +1,131 @@ +#include "ads1259.h" + +#include "ads1259_def.h" +#include "delay.h" +#include "interrupt.h" +#include "log.h" +#include "math.h" + +// Used to determine length of time needed between convert command sent and data collection +static const uint32_t s_conversion_time_ms_lookup[NUM_ADS1259_DATA_RATE] = { + [ADS1259_DATA_RATE_10] = 100, [ADS1259_DATA_RATE_17] = 61, [ADS1259_DATA_RATE_50] = 21, + [ADS1259_DATA_RATE_60] = 17, [ADS1259_DATA_RATE_400] = 3, [ADS1259_DATA_RATE_1200] = 2, + [ADS1259_DATA_RATE_3600] = 1, [ADS1259_DATA_RATE_14400] = 1, +}; + +// Number of noise free bits for each sampling rate +static const uint8_t s_num_usable_bits[NUM_ADS1259_DATA_RATE] = { + [ADS1259_DATA_RATE_10] = 21, [ADS1259_DATA_RATE_17] = 21, [ADS1259_DATA_RATE_50] = 20, + [ADS1259_DATA_RATE_60] = 20, [ADS1259_DATA_RATE_400] = 19, [ADS1259_DATA_RATE_1200] = 18, + [ADS1259_DATA_RATE_3600] = 17, [ADS1259_DATA_RATE_14400] = 16, +}; + +// tx spi command to ads1259 +static void prv_send_command(Ads1259Storage *storage, uint8_t command) { + uint8_t payload[] = { command }; + spi_exchange(storage->spi_port, payload, 1, NULL, 0); +} + +static StatusCode prv_configure_registers(Ads1259Storage *storage) { + uint8_t register_lookup[NUM_CONFIG_REGISTERS] = { + (ADS1259_SPI_TIMEOUT_ENABLE | ADS1259_INTERNAL_REF_BIAS_ENABLE), + (ADS1259_OUT_OF_RANGE_FLAG_ENABLE | ADS1259_CHECK_SUM_ENABLE), + (ADS1259_CONVERSION_CONTROL_MODE_PULSE | ADS1259_DATA_RATE_SPS), + }; + // reset all register values to default + prv_send_command(storage, ADS1259_STOP_READ_DATA_CONTINUOUS); + prv_send_command(storage, ADS1259_RESET); + delay_ms(1); // Needs at least 8 fclk cycles before next command + prv_send_command(storage, ADS1259_STOP_READ_DATA_CONTINUOUS); + uint8_t payload[NUM_REGISTER_WRITE_COMM] = { (ADS1259_WRITE_REGISTER | ADS1259_ADDRESS_CONFIG0), + NUM_CONFIG_REGISTERS - 1, register_lookup[0], + register_lookup[1], register_lookup[2] }; + spi_exchange(storage->spi_port, payload, NUM_REGISTER_WRITE_COMM, NULL, 0); + // sanity check that data was written correctly + uint8_t check_regs[] = { (ADS1259_READ_REGISTER | ADS1259_ADDRESS_CONFIG0), + NUM_ADS1259_REGISTERS - 1 }; + uint8_t reg_readback[NUM_ADS1259_REGISTERS]; + spi_exchange(storage->spi_port, check_regs, sizeof(check_regs), reg_readback, + NUM_ADS1259_REGISTERS); + LOG_DEBUG("ads1259 post init register values:\n"); + for (int i = 0; i < NUM_ADS1259_REGISTERS; i++) { + LOG_DEBUG("reg %d: 0x%x\n", i, reg_readback[i]); + } + return STATUS_CODE_OK; +} + +// calculate check-sum based on page 29 of datasheet +static Ads1259StatusCode prv_checksum(Ads1259Storage *storage) { + uint8_t sum = (uint8_t)(storage->rx_data.LSB + storage->rx_data.MID + storage->rx_data.MSB + + ADS1259_CHECKSUM_OFFSET); + if (storage->rx_data.CHK_SUM & CHK_SUM_FLAG_BIT) { + return ADS1259_STATUS_CODE_OUT_OF_RANGE; + } + if ((sum & ~(CHK_SUM_FLAG_BIT)) != (storage->rx_data.CHK_SUM & ~(CHK_SUM_FLAG_BIT))) { + return ADS1259_STATUS_CODE_CHECKSUM_FAULT; + } + return ADS1259_STATUS_CODE_OK; +} + +// using the amount of noise free bits based on the SPS and VREF calculate analog voltage value +// 0x000000-0x7FFFFF positive range, 0xFFFFFF - 0x800000 neg range, rightmost is greatest magnitude +static void prv_convert_data(Ads1259Storage *storage) { + double resolution = pow(2, s_num_usable_bits[ADS1259_DATA_RATE_SPS] - 1); + + if (storage->conv_data.raw & RX_NEG_VOLTAGE_BIT) { + storage->reading = 0 - ((RX_MAX_VALUE - storage->conv_data.raw) >> + (24 - s_num_usable_bits[ADS1259_DATA_RATE_SPS])) * + EXTERNAL_VREF_V / resolution; + } else { + storage->reading = (storage->conv_data.raw >> (24 - s_num_usable_bits[ADS1259_DATA_RATE_SPS])) * + EXTERNAL_VREF_V / (resolution - 1); + } +} + +static void prv_read_conversion(Ads1259Storage *storage) { + Ads1259StatusCode code; + uint8_t payload[] = { ADS1259_READ_DATA_BY_OPCODE }; + + // Read conversion result + spi_exchange(storage->spi_port, payload, 1, (uint8_t *)&storage->rx_data, NUM_ADS_RX_BYTES); + + code = prv_checksum(storage); + (*storage->handler)(code, storage->error_context); + storage->conv_data.MSB = storage->rx_data.MSB; + storage->conv_data.MID = storage->rx_data.MID; + storage->conv_data.LSB = storage->rx_data.LSB; + + prv_convert_data(storage); +} + +// Initializes ads1259 connection on a SPI port. Can be re-called to calibrate adc +StatusCode ads1259_init(Ads1259Storage *storage, Ads1259Settings *settings) { + storage->spi_port = settings->spi_port; + storage->handler = settings->handler; + storage->error_context = settings->error_context; + const SpiSettings spi_settings = { + .baudrate = settings->spi_baudrate, + .mode = SPI_MODE_1, + .mosi = settings->mosi, + .miso = settings->miso, + .sclk = settings->sclk, + .cs = settings->cs, + }; + status_ok_or_return(spi_init(settings->spi_port, &spi_settings)); + status_ok_or_return(prv_configure_registers(storage)); + LOG_DEBUG("ads1259 driver init all ok\n"); + return STATUS_CODE_OK; +} + +// Reads conversion data to data struct in storage. data->reading gives total value +StatusCode ads1259_get_conversion_data(Ads1259Storage *storage) { + // Send command to begin conversion + prv_send_command(storage, ADS1259_START_CONV); + + // Delay until conversion is complete + delay_ms(s_conversion_time_ms_lookup[ADS1259_DATA_RATE_SPS]); + + // Read conversion + prv_read_conversion(storage); + return STATUS_CODE_OK; +} diff --git a/libraries/ms-drivers/src/ltc_afe.c b/libraries/ms-drivers/src/ltc_afe.c new file mode 100644 index 000000000..63e948b35 --- /dev/null +++ b/libraries/ms-drivers/src/ltc_afe.c @@ -0,0 +1,8 @@ +#include "ltc_afe.h" + +#include "ltc_afe_impl.h" + +StatusCode ltc_afe_init(LtcAfeStorage *afe, const LtcAfeSettings *settings) { + status_ok_or_return(ltc_afe_impl_init(afe, settings)); + return STATUS_CODE_OK; +} diff --git a/libraries/ms-drivers/src/ltc_afe_impl.c b/libraries/ms-drivers/src/ltc_afe_impl.c new file mode 100644 index 000000000..3023303ac --- /dev/null +++ b/libraries/ms-drivers/src/ltc_afe_impl.c @@ -0,0 +1,344 @@ +#include "ltc_afe_impl.h" + +#include +#include + +// Will port in crc15 for now. Worth discussing in the future if we really need it tbh +#include "crc15.h" +#include "delay.h" +#include "log.h" +#include "ltc6811.h" + +// - 12-bit, 16-bit and 24-bit values are little endian +// - commands and PEC are big endian + +static uint16_t s_read_reg_cmd[NUM_LTC_AFE_REGISTERS] = { + [LTC_AFE_REGISTER_CONFIG] = LTC6811_RDCFG_RESERVED, + [LTC_AFE_REGISTER_CELL_VOLTAGE_A] = LTC6811_RDCVA_RESERVED, + [LTC_AFE_REGISTER_CELL_VOLTAGE_B] = LTC6811_RDCVB_RESERVED, + [LTC_AFE_REGISTER_CELL_VOLTAGE_C] = LTC6811_RDCVC_RESERVED, + [LTC_AFE_REGISTER_CELL_VOLTAGE_D] = LTC6811_RDCVD_RESERVED, + [LTC_AFE_REGISTER_AUX_A] = LTC6811_RDAUXA_RESERVED, + [LTC_AFE_REGISTER_AUX_B] = LTC6811_RDAUXB_RESERVED, + [LTC_AFE_REGISTER_STATUS_A] = LTC6811_RDSTATA_RESERVED, + [LTC_AFE_REGISTER_STATUS_B] = LTC6811_RDSTATB_RESERVED, + [LTC_AFE_REGISTER_READ_COMM] = LTC6811_RDCOMM_RESERVED, + [LTC_AFE_REGISTER_START_COMM] = LTC6811_STCOMM_RESERVED +}; + +static uint8_t s_voltage_reg[NUM_LTC_AFE_VOLTAGE_REGISTERS] = { + [LTC_AFE_VOLTAGE_REGISTER_A] = LTC_AFE_REGISTER_CELL_VOLTAGE_A, + [LTC_AFE_VOLTAGE_REGISTER_B] = LTC_AFE_REGISTER_CELL_VOLTAGE_B, + [LTC_AFE_VOLTAGE_REGISTER_C] = LTC_AFE_REGISTER_CELL_VOLTAGE_C, + [LTC_AFE_VOLTAGE_REGISTER_D] = LTC_AFE_REGISTER_CELL_VOLTAGE_D, +}; + +static void prv_wakeup_idle(LtcAfeStorage *afe) { + LtcAfeSettings *settings = &afe->settings; + // Wakeup method 2 - pair of long -1, +1 for each device + for (size_t i = 0; i < settings->num_devices; i++) { + gpio_set_state(&settings->cs, GPIO_STATE_LOW); + gpio_set_state(&settings->cs, GPIO_STATE_HIGH); + // Wait for 300us - greater than tWAKE, less than tIDLE + delay_ms(0.3); + } +} + +static StatusCode prv_build_cmd(uint16_t command, uint8_t *cmd, size_t len) { + if (len != LTC6811_CMD_SIZE) { + return status_code(STATUS_CODE_INVALID_ARGS); + } + + cmd[0] = (uint8_t)(command >> 8); + cmd[1] = (uint8_t)(command & 0xFF); + + uint16_t cmd_pec = crc15_calculate(cmd, 2); + cmd[2] = (uint8_t)(cmd_pec >> 8); + cmd[3] = (uint8_t)(cmd_pec); + + return STATUS_CODE_OK; +} + +static StatusCode prv_read_register(LtcAfeStorage *afe, LtcAfeRegister reg, uint8_t *data, + size_t len) { + if (reg > NUM_LTC_AFE_REGISTERS) { + return status_code(STATUS_CODE_INVALID_ARGS); + } + + uint16_t reg_cmd = s_read_reg_cmd[reg]; + + uint8_t cmd[LTC6811_CMD_SIZE] = { 0 }; + prv_build_cmd(reg_cmd, cmd, LTC6811_CMD_SIZE); + + prv_wakeup_idle(afe); + return spi_exchange(afe->settings.spi_port, cmd, LTC6811_CMD_SIZE, data, len); +} + +// read from a voltage register +static StatusCode prv_read_voltage(LtcAfeStorage *afe, LtcAfeVoltageRegister reg, + LtcAfeVoltageRegisterGroup *data) { + if (reg > NUM_LTC_AFE_VOLTAGE_REGISTERS) { + return status_code(STATUS_CODE_INVALID_ARGS); + } + + size_t len = sizeof(LtcAfeVoltageRegisterGroup) * afe->settings.num_devices; + return prv_read_register(afe, s_voltage_reg[reg], (uint8_t *)data, len); +} + +// start cell voltage conversion +static StatusCode prv_trigger_adc_conversion(LtcAfeStorage *afe) { + LtcAfeSettings *settings = &afe->settings; + uint8_t mode = (uint8_t)((settings->adc_mode + 1) % 3); + // ADCV command + uint16_t adcv = LTC6811_ADCV_RESERVED | LTC6811_ADCV_DISCHARGE_NOT_PERMITTED | + LTC6811_CNVT_CELL_ALL | (mode << 7); + + uint8_t cmd[LTC6811_CMD_SIZE] = { 0 }; + prv_build_cmd(adcv, cmd, LTC6811_CMD_SIZE); + + prv_wakeup_idle(afe); + return spi_exchange(settings->spi_port, cmd, LTC6811_CMD_SIZE, NULL, 0); +} + +static StatusCode prv_trigger_aux_adc_conversion(LtcAfeStorage *afe) { + LtcAfeSettings *settings = &afe->settings; + uint8_t mode = (uint8_t)((settings->adc_mode + 1) % 3); + // ADAX + uint16_t adax = LTC6811_ADAX_RESERVED | LTC6811_ADAX_GPIO1 | (mode << 7); + + uint8_t cmd[LTC6811_CMD_SIZE] = { 0 }; + prv_build_cmd(adax, cmd, LTC6811_CMD_SIZE); + + prv_wakeup_idle(afe); + return spi_exchange(settings->spi_port, cmd, LTC6811_CMD_SIZE, NULL, 0); +} + +static StatusCode prv_aux_write_comm_register(LtcAfeStorage *afe, uint8_t device_cell) { + if (device_cell >= AUX_ADG731_NUM_PINS) { + return STATUS_CODE_OUT_OF_RANGE; + } + LtcAfeSettings *settings = &afe->settings; + LtcAfeWriteCommRegPacket packet = { 0 }; + // Build WRCOMM Command + prv_build_cmd(LTC6811_WRCOMM_RESERVED, packet.wrcomm, LTC6811_CMD_SIZE); + // Write 3 bytes of data to the COMM registers + // We send the a byte and then we send CSBM_HIGH to + // release the SPI port + packet.reg.icom0 = LTC6811_ICOM_CSBM_LOW; + packet.reg.d0 = device_cell; + packet.reg.fcom0 = LTC6811_FCOM_CSBM_HIGH; + packet.reg.icom1 = LTC6811_ICOM_NO_TRANSMIT; + packet.reg.icom2 = LTC6811_ICOM_NO_TRANSMIT; + uint16_t comm_pec = crc15_calculate((uint8_t *)&packet.reg, sizeof(LtcAfeCommRegisterData)); + + prv_wakeup_idle(afe); + return spi_exchange(settings->spi_port, (uint8_t *)&packet, sizeof(LtcAfeWriteCommRegPacket), + NULL, 0); +} + +static StatusCode prv_aux_send_comm_register(LtcAfeStorage *afe) { + LtcAfeSettings *settings = &afe->settings; + LtcAfeSendCommRegPacket packet = { 0 }; + // Build STCOMM command + prv_build_cmd(LTC6811_STCOMM_RESERVED, packet.stcomm, LTC6811_CMD_SIZE); + for (uint8_t i = 0; i < LTC6811_NUM_COMM_REG_BYTES; i++) { + // NULL bytes so our SPI drivers will send 24 clock cycles + packet.clk[i] = 0; + } + prv_wakeup_idle(afe); + return spi_exchange(settings->spi_port, (uint8_t *)&packet, sizeof(LtcAfeSendCommRegPacket), NULL, + 0); +} + +// write config to all devices +static StatusCode prv_write_config(LtcAfeStorage *afe, uint8_t gpio_enable_pins) { + LtcAfeSettings *settings = &afe->settings; + // see p.54 in datasheet + LtcAfeWriteConfigPacket config_packet = { 0 }; + + prv_build_cmd(LTC6811_WRCFG_RESERVED, config_packet.wrcfg, SIZEOF_ARRAY(config_packet.wrcfg)); + + // essentially, each set of CFGR registers are clocked through each device, + // until the first set reaches the last device (like a giant shift register) + // thus, we send CFGR registers starting with the bottom slave in the stack + for (uint8_t curr_device = 0; curr_device < settings->num_devices; curr_device++) { + uint8_t enable = gpio_enable_pins; + + uint16_t undervoltage = 0; + uint16_t overvoltage = 0; + + config_packet.devices[curr_device].reg.discharge_bitset = afe->discharge_bitset[curr_device]; + config_packet.devices[curr_device].reg.discharge_timeout = LTC_AFE_DISCHARGE_TIMEOUT_30_S; + + config_packet.devices[curr_device].reg.adcopt = ((settings->adc_mode + 1) > 3); + config_packet.devices[curr_device].reg.swtrd = true; + + config_packet.devices[curr_device].reg.undervoltage = undervoltage; + config_packet.devices[curr_device].reg.overvoltage = overvoltage; + + // GPIO 1 is used to read data from the mux + config_packet.devices[curr_device].reg.gpio = (enable >> 3); + + uint16_t cfgr_pec = crc15_calculate((uint8_t *)&config_packet.devices[curr_device].reg, 6); + config_packet.devices[curr_device].pec = SWAP_UINT16(cfgr_pec); + } + + size_t len = SIZEOF_LTC_AFE_WRITE_CONFIG_PACKET(settings->num_devices); + prv_wakeup_idle(afe); + return spi_exchange(settings->spi_port, (uint8_t *)&config_packet, len, NULL, 0); +} + +static void prv_calc_offsets(LtcAfeStorage *afe) { + // Our goal is to populate result arrays as if the ignored inputs don't exist. This requires + // converting the actual LTC6811 cell index to some potentially smaller result index. + // + // Since we access the same register across multiple devices, we can't just keep a counter and + // increment it for each new value we get during register access. Instead, we precompute each + // input's corresponding result index. Inputs that are ignored will not be copied into the result + // array. + // + // Similarly, we do the opposite mapping for discharge. + LtcAfeSettings *settings = &afe->settings; + size_t cell_index = 0; + size_t aux_index = 0; + for (size_t device = 0; device < settings->num_devices; device++) { + for (size_t device_cell = 0; device_cell < LTC_AFE_MAX_CELLS_PER_DEVICE; device_cell++) { + size_t cell = device * LTC_AFE_MAX_CELLS_PER_DEVICE + device_cell; + + if ((settings->cell_bitset[device] >> device_cell) & 0x1) { + // Cell input enabled - store the index that this input should be stored in + // when copying to the result array and the opposite for discharge + afe->discharge_cell_lookup[cell_index] = cell; + afe->cell_result_lookup[cell] = cell_index++; + } + + if ((settings->aux_bitset[device] >> device_cell) & 0x1) { + // Cell input enabled - store the index that this input should be stored in + // when copying to the result array + afe->aux_result_lookup[cell] = aux_index++; + } + } + } +} + +StatusCode ltc_afe_impl_init(LtcAfeStorage *afe, const LtcAfeSettings *settings) { + if (settings->num_devices > LTC_AFE_MAX_DEVICES || + settings->num_cells > settings->num_devices * LTC_AFE_MAX_CELLS || + settings->num_thermistors > LTC_AFE_MAX_THERMISTORS) { + // bad no. devices (needs code change) + // bad no. of cells (needs verification) + return status_code(STATUS_CODE_INVALID_ARGS); + } + memset(afe, 0, sizeof(*afe)); + memcpy(&afe->settings, settings, sizeof(afe->settings)); + + prv_calc_offsets(afe); + crc15_init_table(); + + SpiSettings spi_config = { + .baudrate = settings->spi_baudrate, // + .mode = SPI_MODE_3, // + .mosi = settings->mosi, // + .miso = settings->miso, // + .sclk = settings->sclk, // + .cs = settings->cs, + }; + spi_init(settings->spi_port, &spi_config); + + // Use GPIO1 as analog input, GPIO 3-5 for SPI + uint8_t gpio_bits = + LTC6811_GPIO1_PD_OFF | LTC6811_GPIO3_PD_OFF | LTC6811_GPIO4_PD_OFF | LTC6811_GPIO5_PD_OFF; + return prv_write_config(afe, gpio_bits); +} + +StatusCode ltc_afe_impl_trigger_cell_conv(LtcAfeStorage *afe) { + return prv_trigger_adc_conversion(afe); +} + +StatusCode ltc_afe_impl_trigger_aux_conv(LtcAfeStorage *afe, uint8_t device_cell) { + uint8_t gpio_bits = + LTC6811_GPIO1_PD_OFF | LTC6811_GPIO3_PD_OFF | LTC6811_GPIO4_PD_OFF | LTC6811_GPIO5_PD_OFF; + prv_write_config(afe, gpio_bits); + prv_aux_write_comm_register(afe, device_cell); + prv_aux_send_comm_register(afe); + return prv_trigger_aux_adc_conversion(afe); +} + +StatusCode ltc_afe_impl_read_cells(LtcAfeStorage *afe) { + // Read all voltage A, then B, ... + LtcAfeSettings *settings = &afe->settings; + for (uint8_t cell_reg = 0; cell_reg < NUM_LTC_AFE_VOLTAGE_REGISTERS; ++cell_reg) { + LtcAfeVoltageRegisterGroup voltage_register[LTC_AFE_MAX_DEVICES] = { 0 }; + prv_read_voltage(afe, cell_reg, voltage_register); + + for (uint8_t device = 0; device < settings->num_devices; ++device) { + for (uint16_t cell = 0; cell < LTC6811_CELLS_IN_REG; ++cell) { + // LSB of the reading is 100 uV + uint16_t voltage = voltage_register[device].reg.voltages[cell]; + uint16_t device_cell = cell + (cell_reg * LTC6811_CELLS_IN_REG); + uint16_t index = device * LTC_AFE_MAX_CELLS_PER_DEVICE + device_cell; + + if ((settings->cell_bitset[device] >> device_cell) & 0x1) { + // Input enabled - store result + afe->cell_voltages[afe->cell_result_lookup[index]] = voltage; + } + } + + // the Packet Error Code is transmitted after the cell data (see p.45) + uint16_t received_pec = SWAP_UINT16(voltage_register[device].pec); + uint16_t data_pec = crc15_calculate((uint8_t *)&voltage_register[device], 6); + if (received_pec != data_pec) { + // return early on failure + return status_code(STATUS_CODE_INTERNAL_ERROR); + } + } + } + + return STATUS_CODE_OK; +} + +StatusCode ltc_afe_impl_read_aux(LtcAfeStorage *afe, uint8_t device_cell) { + LtcAfeSettings *settings = &afe->settings; + LtcAfeAuxRegisterGroupPacket register_data[LTC_AFE_MAX_DEVICES] = { 0 }; + + size_t len = settings->num_devices * sizeof(LtcAfeAuxRegisterGroupPacket); + prv_read_register(afe, LTC_AFE_REGISTER_AUX_A, (uint8_t *)register_data, len); + + for (uint16_t device = 0; device < settings->num_devices; ++device) { + // data comes in in the form { 1, 1, 2, 2, 3, 3, PEC, PEC } + // we only care about GPIO1 and the PEC + uint16_t voltage = register_data[device].reg.voltages[0]; + + if ((settings->aux_bitset[device] >> device_cell) & 0x1) { + // Input enabled - store result + uint16_t index = device * LTC_AFE_MAX_CELLS_PER_DEVICE + device_cell; + afe->aux_voltages[afe->aux_result_lookup[index]] = voltage; + } + + uint16_t received_pec = SWAP_UINT16(register_data[device].pec); + uint16_t data_pec = crc15_calculate((uint8_t *)®ister_data[device], 6); + if (received_pec != data_pec) { + return status_code(STATUS_CODE_INTERNAL_ERROR); + } + } + + return STATUS_CODE_OK; +} + +StatusCode ltc_afe_impl_toggle_cell_discharge(LtcAfeStorage *afe, uint16_t cell, bool discharge) { + if (cell >= afe->settings.num_cells) { + return status_code(STATUS_CODE_INVALID_ARGS); + } + + uint16_t actual_cell = afe->discharge_cell_lookup[cell]; + uint16_t device_cell = actual_cell % LTC_AFE_MAX_CELLS_PER_DEVICE; + uint16_t device = actual_cell / LTC_AFE_MAX_CELLS_PER_DEVICE; + + if (discharge) { + afe->discharge_bitset[device] |= (1 << device_cell); + } else { + afe->discharge_bitset[device] &= ~(1 << device_cell); + } + + return STATUS_CODE_OK; +} diff --git a/projects/bms_carrier/README.md b/projects/bms_carrier/README.md new file mode 100644 index 000000000..ad1004bff --- /dev/null +++ b/projects/bms_carrier/README.md @@ -0,0 +1,16 @@ + +# bms_carrier + diff --git a/projects/bms_carrier/config.json b/projects/bms_carrier/config.json new file mode 100644 index 000000000..57bd3ced3 --- /dev/null +++ b/projects/bms_carrier/config.json @@ -0,0 +1,9 @@ +{ + "libs": [ + "FreeRTOS", + "ms-common", + "master", + "ms-drivers" + ], + "can": true +} \ No newline at end of file diff --git a/projects/bms_carrier/inc/bms.h b/projects/bms_carrier/inc/bms.h new file mode 100644 index 000000000..cf50066cc --- /dev/null +++ b/projects/bms_carrier/inc/bms.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "cell_sense.h" +#include "current_sense.h" +#include "i2c.h" +#include "status.h" + +#define BMS_PERIPH_I2C_PORT I2C_PORT_2 +#define BMS_PERIPH_I2C_SDA_PIN \ + { GPIO_PORT_B, 11 } +#define BMS_PERIPH_I2C_SCL_PIN \ + { GPIO_PORT_B, 10 } +#define BMS_FAN_ALERT_PIN \ + { GPIO_PORT_A, 9 } + +#define BMS_IO_EXPANDER_I2C_ADDR 0x40 + +#define BMS_FAN_CTRL_1_I2C_ADDR 0x5E +#define BMS_FAN_CTRL_2_I2C_ADDR 0x5F +#define NUM_BMS_FAN_CTRLS 2 + +// Not dealing with debouncer here +typedef struct BmsStorage { + // RelayStorage relay_storage; + CurrentStorage current_storage; + AfeReadings afe_readings; + LtcAfeStorage ltc_afe_storage; + CellSenseStorage cell_storage; + // FanStorage fan_storage_1; + // FanStorage fan_storage_2; + // DebouncerStorage killswitch_storage; + // BpsStorage bps_storage; +} BmsStorage; diff --git a/projects/bms_carrier/inc/cell_sense.h b/projects/bms_carrier/inc/cell_sense.h new file mode 100644 index 000000000..ead8cf1d2 --- /dev/null +++ b/projects/bms_carrier/inc/cell_sense.h @@ -0,0 +1,99 @@ +// Individual AFE FSM module. Initialize it and then it should continuously run by itself +#pragma once + +#include +#include +#include +#include + +#include "fsm.h" +#include "gpio.h" +#include "ltc_afe.h" +#include "spi.h" +#include "status.h" + +#define LTC_AFE_FSM_CELL_CONV_DELAY_MS 10 +#define LTC_AFE_FSM_AUX_CONV_DELAY_MS 6 +// Maximum number of retry attempts to read cell/aux data once triggered +#define LTC_AFE_FSM_MAX_RETRY_COUNT 3 + +#define NUM_LTC_AFE_FSM_STATES 6 +#define NUM_LTC_AFE_FSM_TRANSITIONS 14 + +#define NUM_AFES 3 +#define NUM_CELL_MODULES_PER_AFE 6 +#define NUM_TOTAL_CELLS (NUM_AFES * NUM_CELL_MODULES_PER_AFE) +#define NUM_THERMISTORS (NUM_TOTAL_CELLS * 2) +#define MAX_AFE_FAULTS 5 + +#define AFE_SPI_PORT SPI_PORT_1 +#define AFE_SPI_SS \ + { .port = GPIO_PORT_A, .pin = 4 } +#define AFE_SPI_SCK \ + { .port = GPIO_PORT_A, .pin = 5 } +#define AFE_SPI_MISO \ + { .port = GPIO_PORT_A, .pin = 6 } +#define AFE_SPI_MOSI \ + { .port = GPIO_PORT_A, .pin = 7 } + +// Wraps the LTC AFE module and handles all the sequencing. +// Requires LTC AFE, soft timers to be initialized. +// + +DECLARE_FSM(ltc_afe_fsm); + +typedef enum LtcAfeFsmStateId { + LTC_AFE_IDLE = 0, + LTC_AFE_TRIGGER_CELL_CONV, + LTC_AFE_READ_CELLS, + LTC_AFE_TRIGGER_AUX_CONV, + LTC_AFE_READ_AUX, + LTC_AFE_AUX_COMPLETE, + LTC_AFE_FAULT +} LtcAfeFsmStateId; + +// We can raise a fault using this when transitioning to LTC_AFE_FAULT to identify where it came +// from +typedef enum { + LTC_AFE_FSM_FAULT_TRIGGER_CELL_CONV = 0, + LTC_AFE_FSM_FAULT_READ_ALL_CELLS, + LTC_AFE_FSM_FAULT_TRIGGER_AUX_CONV, + LTC_AFE_FSM_FAULT_READ_AUX, + NUM_LTC_AFE_FSM_FAULTS +} LtcAfeFsmFault; + +typedef struct CellSenseSettings { + // Units are 100 uV (or DeciMilliVolts) + uint16_t undervoltage_dmv; + uint16_t overvoltage_dmv; + uint16_t charge_overtemp_dmv; + uint16_t discharge_overtemp_dmv; +} CellSenseSettings; + +typedef struct AfeReadings { + // TODO(SOFT-9): total_voltage used to be stored here as well + uint16_t voltages[NUM_TOTAL_CELLS]; + uint16_t temps[NUM_THERMISTORS]; +} AfeReadings; + +typedef struct CellSenseStorage { + LtcAfeStorage *afe; + AfeReadings *readings; + uint16_t num_afe_faults; + CellSenseSettings settings; +} CellSenseStorage; + +// First initialize the cell_sense module. +// Since it is the only module using the LTC6811, we can also initialize that and the corresponding +// FSM. Initialize the LTC6811. |settings.cell_bitset| and |settings.aux_bitset| should be an array +// of bitsets where bits 0 to 11 represent whether we should monitor the cell input for the given +// device. prv_extract_cell_result and prv_extract_aux_result will be called when the +// corresponding conversion is completed. + +StatusCode cell_sense_init(const CellSenseSettings *settings, AfeReadings *readings, + LtcAfeStorage *afe, LtcAfeSettings *ltc_settings); + +// Mark cell for discharging (takes effect after config is re-written) +// |cell| should be [0, settings.num_cells) + +StatusCode ltc_afe_toggle_cell_discharge(LtcAfeStorage *afe, uint16_t cell, bool discharge); diff --git a/projects/bms_carrier/inc/current_sense.h b/projects/bms_carrier/inc/current_sense.h new file mode 100644 index 000000000..23da53811 --- /dev/null +++ b/projects/bms_carrier/inc/current_sense.h @@ -0,0 +1,32 @@ +#pragma once + +// Stores current readings from the ADS1259 in a ring buffer. +// Requires interrupts and soft timers to be initialized. + +#include +#include + +#include "spi.h" +#include "status.h" + +#define NUM_STORED_CURRENT_READINGS 20 +#define CURRENT_SENSE_SPI_PORT SPI_PORT_2 + +// slightly larger than conversion time of adc +#define CONVERSION_TIME_MS 18 + +// see current sense on confluence for these values (centiamps) +#define DISCHARGE_OVERCURRENT_CA (13000) // 130 Amps +#define CHARGE_OVERCURRENT_CA (-8160) // -81.6 Amps + +typedef struct CurrentStorage { + int16_t readings_ring[NUM_STORED_CURRENT_READINGS]; + uint16_t ring_idx; + int16_t average; + uint32_t conv_period_ms; // Time in ms between conversions (soft timer kicks) +} CurrentStorage; + +bool current_sense_is_charging(); + +StatusCode current_sense_init(CurrentStorage *readings, SpiSettings *settings, + uint32_t conv_period_ms); diff --git a/projects/bms_carrier/inc/exported_enums.h b/projects/bms_carrier/inc/exported_enums.h new file mode 100644 index 000000000..df07049d3 --- /dev/null +++ b/projects/bms_carrier/inc/exported_enums.h @@ -0,0 +1,210 @@ +#pragma once + +// This file stores enums which are exported between projects to allow both +// sides to use the same enums when sending and receiving CAN Messages over the +// primary network. To make things easier all enums in this file must follow a +// slightly modified naming convention. +// +// Example: +// typedef enum { +// EE___ = 0, +// // ... +// NUM_EE___, +// } EE + +typedef enum { + EE_CONSOLE_FAULT_AREA_DRIVE_FSM = 0, + EE_CONSOLE_FAULT_AREA_POWER_MAIN, + EE_CONSOLE_FAULT_AREA_POWER_OFF, + EE_CONSOLE_FAULT_AREA_POWER_AUX, + EE_CONSOLE_FAULT_AREA_BPS_HEARTBEAT, + NUM_EE_CONSOLE_FAULT_AREAS +} EEConsoleFaultArea; + +typedef enum { + EE_DRIVE_FSM_STEP_MCI_RELAY_STATE = 0, + EE_DRIVE_FSM_STEP_PRECHARGE_TIMEOUT, + EE_DRIVE_FSM_STEP_EBRAKE_STATE, + EE_DRIVE_FSM_STEP_MCI_OUTPUT, + NUM_EE_DRIVE_FSM_STEPS +} EEDriveFsmStep; + +typedef enum { + EE_POWER_MAIN_SEQUENCE_CONFIRM_AUX_STATUS = 0, + EE_POWER_MAIN_SEQUENCE_TURN_ON_DRIVER_BMS, + EE_POWER_MAIN_SEQUENCE_CONFIRM_BATTERY_STATUS, + EE_POWER_MAIN_SEQUENCE_CLOSE_BATTERY_RELAYS, + EE_POWER_MAIN_SEQUENCE_CONFIRM_DCDC, + EE_POWER_MAIN_SEQUENCE_TURN_ON_EVERYTHING, + NUM_EE_POWER_MAIN_SEQUENCES +} EEPowerMainSequence; + +typedef enum { + EE_POWER_OFF_SEQUENCE_DISCHARGE_PRECHARGE = 0, + EE_POWER_OFF_SEQUENCE_TURN_OFF_EVERYTHING, + EE_POWER_OFF_SEQUENCE_OPEN_BATTERY_RELAYS, + NUM_EE_POWER_OFF_SEQUENCES +} EEPowerOffSequence; + +typedef enum { + EE_POWER_AUX_SEQUENCE_CONFIRM_AUX_STATUS = 0, + EE_POWER_AUX_SEQUENCE_TURN_ON_EVERYTHING, + NUM_EE_POWER_AUX_SEQUENCES +} EEPowerAuxSequence; + +typedef enum { + EE_CHARGER_SET_RELAY_STATE_OPEN = 0, + EE_CHARGER_SET_RELAY_STATE_CLOSE, + NUM_EE_CHARGER_SET_RELAY_STATES, +} EEChargerSetRelayState; + +typedef enum { + EE_CHARGER_CONN_STATE_DISCONNECTED = 0, + EE_CHARGER_CONN_STATE_CONNECTED, + NUM_EE_CHARGER_CONN_STATES, +} EEChargerConnState; + +typedef enum { + EE_DRIVE_OUTPUT_OFF = 0, + EE_DRIVE_OUTPUT_DRIVE, + EE_DRIVE_OUTPUT_REVERSE, + NUM_EE_DRIVE_OUTPUTS, +} EEDriveOutput; + +typedef enum { + EE_RELAY_ID_BATTERY = 0, + EE_RELAY_ID_MOTOR_CONTROLLER, + EE_RELAY_ID_SOLAR, + NUM_EE_RELAY_IDS, +} EERelayId; + +// Light type to be used with a SYSTEM_CAN_MESSAGE_LIGHTS_STATE message. +typedef enum EELightType { + EE_LIGHT_TYPE_DRL = 0, + EE_LIGHT_TYPE_BRAKES, + EE_LIGHT_TYPE_STROBE, + EE_LIGHT_TYPE_SIGNAL_RIGHT, + EE_LIGHT_TYPE_SIGNAL_LEFT, + EE_LIGHT_TYPE_SIGNAL_HAZARD, + EE_LIGHT_TYPE_HIGH_BEAMS, + EE_LIGHT_TYPE_LOW_BEAMS, + NUM_EE_LIGHT_TYPES, +} EELightType; + +// Light state to be used with a SYSTEM_CAN_MESSAGE_LIGHTS message. +typedef enum EELightState { + EE_LIGHT_STATE_OFF = 0, // + EE_LIGHT_STATE_ON, // + NUM_EE_LIGHT_STATES, // +} EELightState; + +// Horn state, used with a SYSTEM_CAN_MESSAGE_HORN message. +typedef enum EEHornState { + EE_HORN_STATE_OFF = 0, // + EE_HORN_STATE_ON, // + NUM_EE_HORN_STATES, // +} EEHornState; + +typedef enum { + EE_RELAY_STATE_OPEN = 0, + EE_RELAY_STATE_CLOSE, + NUM_EE_RELAY_STATES, +} EERelayState; + +// For battery heartbeat +typedef enum EEBatteryHeartbeatFaultSource { + EE_BPS_FAULT_SOURCE_KILLSWITCH = 0, + EE_BPS_FAULT_SOURCE_AFE_CELL, + EE_BPS_FAULT_SOURCE_AFE_TEMP, + EE_BPS_FAULT_SOURCE_AFE_FSM, + EE_BPS_FAULT_SOURCE_RELAY, + EE_BPS_FAULT_SOURCE_CURRENT_SENSE, + EE_BPS_FAULT_SOURCE_ACK_TIMEOUT, + NUM_EE_BPS_FAULT_SOURCES, +} EEBatteryHeartbeatFaultSource; + +// Battery heartbeat bitset representing fault reason +typedef uint8_t EEBatteryHeartbeatState; +#define EE_BPS_STATE_OK 0x0 +#define EE_BPS_STATE_FAULT_KILLSWITCH (1 << EE_BPS_FAULT_SOURCE_KILLSWITCH) +#define EE_BPS_STATE_FAULT_AFE_CELL (1 << EE_BPS_FAULT_SOURCE_AFE_CELL) +#define EE_BPS_STATE_FAULT_AFE_TEMP (1 << EE_BPS_FAULT_SOURCE_AFE_TEMP) +#define EE_BPS_STATE_FAULT_AFE_FSM (1 << EE_BPS_FAULT_SOURCE_AFE_FSM) +#define EE_BPS_STATE_FAULT_RELAY (1 << EE_BPS_FAULT_SOURCE_RELAY) +#define EE_BPS_STATE_FAULT_CURRENT_SENSE (1 << EE_BPS_FAULT_SOURCE_CURRENT_SENSE) +#define EE_BPS_STATE_FAULT_ACK_TIMEOUT (1 << EE_BPS_FAULT_SOURCE_ACK_TIMEOUT) + +typedef enum { + EE_DRIVE_STATE_DRIVE = 0, + EE_DRIVE_STATE_NEUTRAL, + EE_DRIVE_STATE_REVERSE, + EE_DRIVE_STATE_PARKING, + NUM_EE_DRIVE_STATES, +} EEDriveState; + +typedef enum { + EE_CRUISE_CONTROL_COMMAND_TOGGLE = 0, + EE_CRUISE_CONTROL_COMMAND_INCREASE, + EE_CRUISE_CONTROL_COMMAND_DECREASE, + NUM_EE_CRUISE_CONTROL_COMMANDS +} EECruiseControl; + +#define EE_PEDAL_VALUE_DENOMINATOR ((1 << 12)) + +typedef enum EEChargerFault { + EE_CHARGER_FAULT_HARDWARE_FAILURE = 0, + EE_CHARGER_FAULT_OVER_TEMP, + EE_CHARGER_FAULT_WRONG_VOLTAGE, + EE_CHARGER_FAULT_POLARITY_FAILURE, + EE_CHARGER_FAULT_COMMUNICATION_TIMEOUT, + EE_CHARGER_FAULT_CHARGER_OFF, + NUM_EE_CHARGER_FAULTS, +} EEChargerFault; + +typedef enum EESolarFault { + // An MCP3427 is faulting too much, data is the solar DataPoint associated with the faulty MCP3427 + EE_SOLAR_FAULT_MCP3427 = 0, + + // An MPPT had an overcurrent, the least significant 4 bits of data is the index of the MPPT + // that faulted, the most significant 4 bits is a 4-bit bitmask of which branches faulted. + EE_SOLAR_FAULT_MPPT_OVERCURRENT, + + // An MPPT had an overvoltage or overtemperature, data is the index of the MPPT that faulted + EE_SOLAR_FAULT_MPPT_OVERVOLTAGE, + EE_SOLAR_FAULT_MPPT_OVERTEMPERATURE, + + // The current from the whole array is over the threshold. No data. + EE_SOLAR_FAULT_OVERCURRENT, + + // The current from the whole array is negative, so we aren't charging. No data. + EE_SOLAR_FAULT_NEGATIVE_CURRENT, + + // The sum of the sensed voltages is over the threshold. No data. + EE_SOLAR_FAULT_OVERVOLTAGE, + + // The temperature of any array thermistor is over our threshold. Data is the index of the too-hot + // thermistor. + EE_SOLAR_FAULT_OVERTEMPERATURE, + + // The temperature of the fan is over the threshold. No data. + EE_SOLAR_FAULT_FAN_OVERTEMPERATURE, + + // Fan failure detected. No data. + EE_SOLAR_FAULT_FAN_FAIL, + + // Relay failure to open + EE_SOLAR_RELAY_OPEN_ERROR, + + NUM_EE_SOLAR_FAULTS, +} EESolarFault; + +typedef enum EESolarRelayOpenErrorReason { + // The drv120 relay has signaled that overtemp/undervolt lockout conditions have been triggered + EE_SOLAR_RELAY_ERROR_DRV120, + // The drv120 relay has not opened or the current has exceeded + EE_SOLAR_RELAY_ERROR_CURRENT_EXCEEDED_NOT_OPEN, + // The drv120 relay's current has not been set + EE_RELAY_ERROR_CURRENT_NEVER_SET, + + NUM_EE_SOLAR_RELAY_OPEN_ERROR_REASON +} EESolarRelayOpenErrorReason; diff --git a/projects/bms_carrier/inc/fault_bps.h b/projects/bms_carrier/inc/fault_bps.h new file mode 100644 index 000000000..2bfd1c690 --- /dev/null +++ b/projects/bms_carrier/inc/fault_bps.h @@ -0,0 +1,10 @@ +#pragma once + +#include "bms.h" +#include "status.h" + +StatusCode fault_bps_init(BmsStorage *storage); + +StatusCode fault_bps_set(uint8_t fault_bitmask); + +StatusCode fault_bps_clear(uint8_t fault_bitmask); diff --git a/projects/bms_carrier/src/current_sense.c b/projects/bms_carrier/src/current_sense.c new file mode 100644 index 000000000..bb6c84a43 --- /dev/null +++ b/projects/bms_carrier/src/current_sense.c @@ -0,0 +1,97 @@ +#include "current_sense.h" + +#include + +#include "ads1259.h" +#include "bms.h" +#include "exported_enums.h" +#include "fault_bps.h" +#include "log.h" +#include "soft_timer.h" + +static Ads1259Storage s_ads1259_storage; +static CurrentStorage *s_current_storage; +static SoftTimer s_timer; +static bool s_is_charging; + +static void prv_ads_error_cb(Ads1259StatusCode code, void *context) { + if (code == ADS1259_STATUS_CODE_OUT_OF_RANGE) { + LOG_WARN("ADS1259 ERROR: OUT OF RANGE\n"); + } else if (code == ADS1259_STATUS_CODE_CHECKSUM_FAULT) { + LOG_WARN("ADS1259 ERROR: CHECKSUM FAULT\n"); + } + + if (code != ADS1259_STATUS_CODE_OK) { + fault_bps_set(EE_BPS_STATE_FAULT_CURRENT_SENSE); + } else { + fault_bps_clear(EE_BPS_STATE_FAULT_CURRENT_SENSE); + } +} + +// returns value in centiamps +static int16_t prv_voltage_to_current(double reading) { + // current = voltage * 100, see confluence + return (int16_t)(100 * 100 * reading); +} + +static void prv_periodic_ads_read(SoftTimerId id) { + // ADC reading + double reading = s_ads1259_storage.reading; + // Convert ADC value to current + int16_t val = prv_voltage_to_current(reading); + + // Update the current sense storage + s_current_storage->readings_ring[s_current_storage->ring_idx] = val; + s_current_storage->ring_idx = (s_current_storage->ring_idx + 1) % NUM_STORED_CURRENT_READINGS; + ads1259_get_conversion_data(&s_ads1259_storage); + // Kick new soft timer + soft_timer_start(s_current_storage->conv_period_ms, prv_periodic_ads_read, &s_timer); + + // update average + int32_t sum = 0; + for (uint16_t i = 0; i < NUM_STORED_CURRENT_READINGS; i++) { + sum += s_current_storage->readings_ring[i]; + } + s_current_storage->average = sum / NUM_STORED_CURRENT_READINGS; + + // update s_is_charging + // note that a negative value indicates the battery is charging + s_is_charging = s_current_storage->average < 0; + + // check faults + if (s_current_storage->average > DISCHARGE_OVERCURRENT_CA || + s_current_storage->average < CHARGE_OVERCURRENT_CA) { + fault_bps_set(EE_BPS_STATE_FAULT_CURRENT_SENSE); + } else { + fault_bps_clear(EE_BPS_STATE_FAULT_CURRENT_SENSE); + } +} + +bool current_sense_is_charging() { + return s_is_charging; +} + +StatusCode current_sense_init(CurrentStorage *storage, SpiSettings *settings, + uint32_t conv_period_ms) { + // Clear the current CurrentStorage + memset(storage, 0, sizeof(CurrentStorage)); + // Initialize static pointer to caller's struct + s_current_storage = storage; + // Setup ADS settings + const Ads1259Settings ads_settings = { + .spi_port = CURRENT_SENSE_SPI_PORT, + .spi_baudrate = settings->baudrate, + .mosi = settings->mosi, + .miso = settings->miso, + .sclk = settings->sclk, + .cs = settings->cs, + .handler = prv_ads_error_cb, + .error_context = NULL, + }; + // Update conversion period (soft timer period) + s_current_storage->conv_period_ms = conv_period_ms; + status_ok_or_return(ads1259_init(&s_ads1259_storage, &ads_settings)); + ads1259_get_conversion_data(&s_ads1259_storage); + soft_timer_start(s_current_storage->conv_period_ms, prv_periodic_ads_read, &s_timer); + return STATUS_CODE_OK; +} diff --git a/projects/bms_carrier/src/fault_bps.c b/projects/bms_carrier/src/fault_bps.c new file mode 100644 index 000000000..0df39363f --- /dev/null +++ b/projects/bms_carrier/src/fault_bps.c @@ -0,0 +1,27 @@ +#include "fault_bps.h" + +#include "bms.h" +#include "exported_enums.h" + +static BmsStorage *s_storage; + +StatusCode fault_bps_init(BmsStorage *storage) { + s_storage = storage; + return STATUS_CODE_OK; +} + +// TODO: These faulting mechanism will be changing substantially +// Fault BPS and open relays +StatusCode fault_bps_set(uint8_t fault_bitmask) { + // s_storage->bps_storage.fault_bitset |= fault_bitmask; + // if (fault_bitmask != EE_BPS_STATE_FAULT_RELAY) { + // relay_fault(&s_storage->relay_storage); + // } + return STATUS_CODE_OK; +} + +// Clear fault from fault_bitmask +StatusCode fault_bps_clear(uint8_t fault_bitmask) { + // s_storage->bps_storage.fault_bitset &= ~(fault_bitmask); + return STATUS_CODE_OK; +} diff --git a/projects/bms_carrier/src/main.c b/projects/bms_carrier/src/main.c new file mode 100644 index 000000000..a23b14bff --- /dev/null +++ b/projects/bms_carrier/src/main.c @@ -0,0 +1,24 @@ +#include + +#include "log.h" +#include "master_task.h" +#include "tasks.h" + +void run_fast_cycle() {} + +void run_medium_cycle() {} + +void run_slow_cycle() {} + +int main() { + tasks_init(); + log_init(); + LOG_DEBUG("Welcome to TEST!"); + + init_master_task(); + + tasks_start(); + + LOG_DEBUG("exiting main?"); + return 0; +} diff --git a/smoke/pd_load_switch_validate/README.md b/smoke/pd_load_switch_validate/README.md new file mode 100644 index 000000000..53b407189 --- /dev/null +++ b/smoke/pd_load_switch_validate/README.md @@ -0,0 +1,16 @@ + +# pd_load_switch_validate + diff --git a/smoke/pd_load_switch_validate/config.json b/smoke/pd_load_switch_validate/config.json new file mode 100644 index 000000000..4019f4748 --- /dev/null +++ b/smoke/pd_load_switch_validate/config.json @@ -0,0 +1,6 @@ +{ + "libs": [ + "FreeRTOS", + "ms-common" + ] +} \ No newline at end of file diff --git a/smoke/pd_load_switch_validate/src/main.c b/smoke/pd_load_switch_validate/src/main.c new file mode 100644 index 000000000..e507672e0 --- /dev/null +++ b/smoke/pd_load_switch_validate/src/main.c @@ -0,0 +1,60 @@ +#include + +#include "delay.h" +#include "log.h" +#include "master_task.h" +#include "pca_config.h" +#include "tasks.h" + +Pca9555GpioAddress addy[] = { + { .i2c_address = I2C_ADDRESS_1, .pin = PCA9555_PIN_IO1_0 }, + // add more addresses to test as needed. +}; +static I2CSettings settings = { + .speed = I2C_SPEED_STANDARD, + .sda = { .port = GPIO_PORT_B, .pin = 9 }, + .scl = { .port = GPIO_PORT_B, .pin = 8 }, +}; +uint8_t data[] = { 0x2, 0x1F, 0xAF }; +Pca9555GpioSettings pca_settings = { .direction = PCA9555_GPIO_DIR_IN, .state = GPIO_STATE_LOW }; + +TASK(master_task, TASK_MIN_STACK_SIZE) { + size_t num_pins = sizeof(addy) / sizeof(Pca9555GpioAddress); + // for (unsigned int i = 0; i < num_pins; ++i) { + // pca9555_gpio_init_pin(&addy[0], &pca_settings); + // } + while (true) { + // Try to turn on all the PCA Expander pins + uint8_t address = 0x24; + i2c_write(I2C_PORT_1, address, data, SIZEOF_ARRAY(data)); + // for (unsigned int i = 0; i < num_pins; ++i) { + // pca9555_gpio_set_state(&addy[i], PCA9555_GPIO_STATE_HIGH); + // } + // for(unsigned int i = 0; i < num_pins; ++i) { + // pca9555_gpio_set_state(&addy[i], PCA9555_GPIO_STATE_HIGH); + // } + // delay_ms(200); + // for(unsigned int i = 0; i < num_pins; ++i) { + // pca9555_gpio_set_state(&addy[i], PCA9555_GPIO_STATE_LOW); + // } + } +} + +int main() { + tasks_init(); + log_init(); + gpio_init(); + gpio_it_init(); + + i2c_init(I2C_PORT_1, &settings); + pca9555_gpio_init(0, I2C_ADDRESS_1); + + LOG_DEBUG("Welcome to TEST!"); + + tasks_init_task(master_task, TASK_PRIORITY(2), NULL); + + tasks_start(); + + LOG_DEBUG("exiting main?"); + return 0; +}