Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bms afe porting #209

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions projects/bms_carrier/src/cell_sense.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
#include "cell_sense.h"

#include <string.h>

#include "current_sense.h"
#include "exported_enums.h"
#include "fault_bps.h"
#include "log.h"
#include "ltc_afe_impl.h"
#include "soft_timer.h"
#include "status.h"

// For now, transition to IDLE
// TODO: Create some kind of fault mechanism if driver function fails
bool raise_fault = false;

FSM(ltc_afe_fsm, NUM_LTC_AFE_FSM_STATES);

static CellSenseStorage s_storage = { 0 };

static void prv_extract_cell_result(uint16_t *result_arr, size_t len, void *context) {
memcpy(s_storage.readings->voltages, result_arr, sizeof(s_storage.readings->voltages));

bool fault = false;
for (size_t i = 0; i < len; i++) {
// s_storage.total_voltage += s_storage.readings->voltages[i];
if (s_storage.readings->voltages[i] < s_storage.settings.undervoltage_dmv ||
s_storage.readings->voltages[i] > s_storage.settings.overvoltage_dmv) {
fault = true;
}
}

// TODO: Find what we should do when encountering faulty results
if (fault) {
fault_bps_set(EE_BPS_STATE_FAULT_AFE_CELL);
} else {
fault_bps_clear(EE_BPS_STATE_FAULT_AFE_CELL);
}
}

static void prv_extract_aux_result(uint16_t *result_arr, size_t len, void *context) {
memcpy(s_storage.readings->temps, result_arr, sizeof(s_storage.readings->temps));

uint16_t threshold = s_storage.settings.discharge_overtemp_dmv;
if (current_sense_is_charging()) threshold = s_storage.settings.charge_overtemp_dmv;

for (size_t i = 0; i < len; ++i) {
if (s_storage.readings->temps[i] > threshold) {
// TODO: Find what we should do when encountering faulty results
fault_bps_set(EE_BPS_STATE_FAULT_AFE_TEMP);
return;
}
}

fault_bps_clear(EE_BPS_STATE_FAULT_AFE_TEMP);
}

static void prv_afe_idle_output(void *context) {
if (raise_fault) {
// TODO: Implement some kind of error handling
}
LOG_DEBUG("Transitioned to IDLE state.\n");
}

static void prv_afe_idle_input(Fsm *fsm, void *context) {
LtcAfeStorage *afe = context;
// Always transition to trigger_aux. Might remove this state tbh
if (!raise_fault) {
fsm_transition(fsm, LTC_AFE_TRIGGER_CELL_CONV);
}
}

static void prv_afe_trigger_cell_conv_output(void *context) {
LtcAfeStorage *afe = context;
StatusCode ret = ltc_afe_impl_trigger_cell_conv(afe);
if (ret != STATUS_CODE_OK) {
raise_fault = true;
}
afe->time_elapsed = 1000 * (xTaskGetTickCount() / 1024);
LOG_DEBUG("Transitioned to TRIGGER CELLS CONVERSION state.\n");
}

static void prv_afe_trigger_cell_conv_input(Fsm *fsm, void *context) {
// Transition to read_cells or idle state
LtcAfeStorage *afe = context;
if (raise_fault) {
fsm_transition(fsm, LTC_AFE_IDLE);
}

uint16_t current_time = 1000 * (xTaskGetTickCount() / 1024);
if (current_time - afe->time_elapsed > LTC_AFE_FSM_CELL_CONV_DELAY_MS) {
afe->retry_count = 0;
fsm_transition(fsm, LTC_AFE_READ_CELLS);
}
}

static void prv_afe_read_cells_output(void *context) {
LtcAfeStorage *afe = context;

StatusCode ret = ltc_afe_impl_read_cells(afe);

if (ret != STATUS_CODE_OK) {
raise_fault = true;
}
LOG_DEBUG("Transitioned to READ CELLS state.\n");
}

static void prv_afe_read_cells_input(Fsm *fsm, void *context) {
// Transition to trigger_aux if no faults have occurred
LtcAfeStorage *afe = context;
if (raise_fault) {
if (afe->retry_count < LTC_AFE_FSM_MAX_RETRY_COUNT) {
afe->retry_count++;
fsm_transition(fsm, LTC_AFE_READ_CELLS);
} else {
fsm_transition(fsm, LTC_AFE_IDLE);
}
}
prv_extract_cell_result(afe->cell_voltages, afe->settings.num_cells,
afe->settings.result_context);
fsm_transition(fsm, LTC_AFE_TRIGGER_AUX_CONV);
}

static void prv_afe_trigger_aux_conv_output(void *context) {
LtcAfeStorage *afe = context;
uint32_t device_cell = afe->device_cell;
StatusCode ret = ltc_afe_impl_trigger_aux_conv(afe, device_cell);
if (ret == STATUS_CODE_OK) {
afe->aux_index = device_cell;
} else {
raise_fault = true;
}
afe->time_elapsed = 1000 * (xTaskGetTickCount() / 1024);
LOG_DEBUG("Transitioned to TRIGGER AUX CONVERSION state.");
}

static void prv_afe_trigger_aux_conv_input(Fsm *fsm, void *context) {
// Transition to read_aux or idle state
LtcAfeStorage *afe = context;
if (raise_fault) {
fsm_transition(fsm, LTC_AFE_IDLE);
}

uint16_t current_time = 1000 * (xTaskGetTickCount() / 1024);
if (current_time - afe->time_elapsed > LTC_AFE_FSM_AUX_CONV_DELAY_MS) {
afe->retry_count = 0;
fsm_transition(fsm, LTC_AFE_READ_AUX);
}
}

static void prv_afe_read_aux_output(void *context) {
LtcAfeStorage *afe = context;
StatusCode ret = ltc_afe_impl_read_aux(afe, afe->device_cell);
if (ret == STATUS_CODE_OK) {
afe->device_cell++;
} else {
raise_fault = true;
}
LOG_DEBUG("Transitioned to READ AUX OUTPUT state.");
}

static void prv_afe_read_aux_input(Fsm *fsm, void *context) {
// Transition to aux_complete, read_aux, trigger_aux_conv, or idle state
LtcAfeStorage *afe = context;
if (raise_fault) {
if (afe->retry_count < LTC_AFE_FSM_MAX_RETRY_COUNT) {
afe->retry_count++;
fsm_transition(fsm, LTC_AFE_READ_AUX);
} else {
fsm_transition(fsm, LTC_AFE_IDLE);
}
}
if (afe->device_cell == afe->settings.num_thermistors) {
afe->device_cell = 0;
fsm_transition(fsm, LTC_AFE_AUX_COMPLETE);
} else {
fsm_transition(fsm, LTC_AFE_TRIGGER_AUX_CONV);
}
}

static void prv_afe_aux_complete_output(void *context) {
LOG_DEBUG("Transitioned to AUX COMPLETE state.");
}

static void prv_afe_aux_complete_input(Fsm *fsm, void *context) {
// We can add broadcasting functionality here later (MVP for now)
LtcAfeStorage *afe = context;
// 12 aux conversions complete - the array should be fully populated
prv_extract_aux_result(afe->aux_voltages, afe->settings.num_cells, afe->settings.result_context);
}

// Declare states
static FsmState s_ltc_afe_state_list[NUM_LTC_AFE_FSM_STATES] = {
STATE(LTC_AFE_IDLE, prv_afe_idle_input, prv_afe_idle_output),
STATE(LTC_AFE_TRIGGER_CELL_CONV, prv_afe_trigger_cell_conv_input,
prv_afe_trigger_cell_conv_output),
STATE(LTC_AFE_READ_CELLS, prv_afe_read_cells_input, prv_afe_read_cells_output),
STATE(LTC_AFE_TRIGGER_AUX_CONV, prv_afe_trigger_aux_conv_input, prv_afe_trigger_aux_conv_output),
STATE(LTC_AFE_READ_AUX, prv_afe_read_aux_input, prv_afe_read_aux_output),
STATE(LTC_AFE_AUX_COMPLETE, prv_afe_aux_complete_input, prv_afe_aux_complete_output),
};

// Declare transitions
static bool s_ltc_afe_transitions[NUM_LTC_AFE_FSM_TRANSITIONS][NUM_LTC_AFE_FSM_TRANSITIONS] = {
TRANSITION(LTC_AFE_IDLE, LTC_AFE_TRIGGER_CELL_CONV),
TRANSITION(LTC_AFE_TRIGGER_CELL_CONV, LTC_AFE_READ_CELLS),
TRANSITION(LTC_AFE_TRIGGER_CELL_CONV, LTC_AFE_IDLE),
TRANSITION(LTC_AFE_READ_CELLS, LTC_AFE_IDLE),
TRANSITION(LTC_AFE_READ_CELLS, LTC_AFE_TRIGGER_AUX_CONV),
TRANSITION(LTC_AFE_READ_CELLS, LTC_AFE_READ_CELLS),
TRANSITION(LTC_AFE_TRIGGER_AUX_CONV, LTC_AFE_READ_AUX),
TRANSITION(LTC_AFE_TRIGGER_AUX_CONV, LTC_AFE_IDLE),
TRANSITION(LTC_AFE_READ_AUX, LTC_AFE_AUX_COMPLETE),
TRANSITION(LTC_AFE_READ_AUX, LTC_AFE_READ_AUX),
TRANSITION(LTC_AFE_READ_AUX, LTC_AFE_TRIGGER_AUX_CONV),
TRANSITION(LTC_AFE_READ_AUX, LTC_AFE_IDLE),
TRANSITION(LTC_AFE_AUX_COMPLETE, LTC_AFE_TRIGGER_CELL_CONV),
};

StatusCode prv_init_ltc_afe_fsm(LtcAfeStorage *afe) {
fsm_init(ltc_afe_fsm, s_ltc_afe_state_list, s_ltc_afe_transitions, LTC_AFE_IDLE, afe);
return STATUS_CODE_OK;
}

StatusCode cell_sense_init(const CellSenseSettings *settings, AfeReadings *afe_readings,
LtcAfeStorage *afe, LtcAfeSettings *ltc_settings) {
s_storage.afe = afe;
s_storage.readings = afe_readings;
memset(afe_readings, 0, sizeof(AfeReadings));
memcpy(&s_storage.settings, settings, sizeof(CellSenseSettings));
status_ok_or_return(ltc_afe_init(afe, ltc_settings));
return prv_init_ltc_afe_fsm(afe);
}

StatusCode ltc_afe_toggle_cell_discharge(LtcAfeStorage *afe, uint16_t cell, bool discharge) {
return ltc_afe_impl_toggle_cell_discharge(afe, cell, discharge);
}
55 changes: 55 additions & 0 deletions py/scripts/can_send.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Sends user-specified CAN messages, without needing to first send them through the STM32."""

import cantools
import can_util
from message_defs import BABYDRIVER_DEVICE_ID

# global var for the Database from load_dbc
# pylint: disable=invalid-name
dbc_database = None


def can_send_raw(msg_id, data, device_id=BABYDRIVER_DEVICE_ID, channel=None):
"""
A wrapper over can_utils.send_message providing a friendlier interface to can_util.send_message.
Args:
msg_id: CAN message ID
(if it's for babydriver messages, then msg_id=BABYDRIVER_CAN_MESSAGE_ID)
data: a list of up to 8 bytes of data to send
device_id: the device ID to send from
channel: CAN channel
"""
can_util.send_message(
data=data,
channel=channel,
msg_id=msg_id,
device_id=device_id,
)


def load_dbc(dbc_filename):
"""
Creates a Database object from an existing DBC file used to encode CAN messages.
Args:
dbc_filename: a string representing the path to a DBC file
"""
# pylint: disable=global-statement
global dbc_database
dbc_database = cantools.database.load_file(dbc_filename)


def can_send(msg_name, channel=None, **data):
"""
Uses the Database object created by the load_dbc function to encode a CAN message
Args:
msg_name: the string name of the message
channel: CAN channel
data: a dictionary of field names (strings) to values (integers)
"""
msg_obj = dbc_database.get_message_by_name(msg_name.upper())
msg_obj_frame_id = msg_obj.frame_id
msg_obj_data = msg_obj.encode(data)

bus = can_util.get_bus(channel)
can_msg = can_util.Message(arbitration_id=msg_obj_frame_id, data=msg_obj_data)
bus.send(can_msg.msg)
Loading
Loading