2205 lines
54 KiB
C
2205 lines
54 KiB
C
/*! General BMS Library
|
|
***************************************************************************/
|
|
/**
|
|
* @file ADBMS181x.cpp
|
|
* @author BMS (bms.support@analog.com)
|
|
|
|
********************************************************************************
|
|
* Copyright 2019(c) Analog Devices, Inc.
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* - Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* - Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* - Neither the name of Analog Devices, Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
* - The use of this software may or may not infringe the patent rights
|
|
* of one or more patent holders. This license does not release you
|
|
* from the requirement that you obtain separate licenses from these
|
|
* patent holders to use this software.
|
|
* - Use of the software either in source or binary form, must be run
|
|
* on or directly connected to an Analog Devices Inc. component.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT,
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*******************************************************************************/
|
|
|
|
/*! @file
|
|
Library for ADBMS181x Multi-cell Battery Monitor
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include "ADBMS181x.h"
|
|
#include "bms_hardware.h"
|
|
|
|
#ifdef LINDUINO
|
|
#include <Arduino.h>
|
|
#endif
|
|
|
|
/* Wake isoSPI up from IDlE state and enters the READY state */
|
|
void wakeup_idle(uint8_t total_ic) // Number of ICs in the system
|
|
{
|
|
for (int i = 0; i < total_ic; i++)
|
|
{
|
|
cs_low(CS_PIN);
|
|
spi_read_byte(0xff); // Guarantees the isoSPI will be in ready mode
|
|
cs_high(CS_PIN);
|
|
}
|
|
}
|
|
|
|
/* Generic wakeup command to wake the ADBMS181x from sleep state */
|
|
void wakeup_sleep(uint8_t total_ic) // Number of ICs in the system
|
|
{
|
|
for (int i = 0; i < total_ic; i++)
|
|
{
|
|
cs_low(CS_PIN);
|
|
delay_u(300); // Guarantees the ADBMS181x will be in standby
|
|
cs_high(CS_PIN);
|
|
delay_u(10);
|
|
}
|
|
}
|
|
|
|
/* Generic function to write 68xx commands. Function calculates PEC for tx_cmd data. */
|
|
void cmd_68(uint8_t tx_cmd[2]) // The command to be transmitted
|
|
{
|
|
uint8_t cmd[4];
|
|
uint16_t cmd_pec;
|
|
uint8_t md_bits;
|
|
|
|
cmd[0] = tx_cmd[0];
|
|
cmd[1] = tx_cmd[1];
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_array(4, cmd);
|
|
cs_high(CS_PIN);
|
|
}
|
|
|
|
/*
|
|
Generic function to write 68xx commands and write payload data.
|
|
Function calculates PEC for tx_cmd data and the data to be transmitted.
|
|
*/
|
|
void write_68(uint8_t total_ic, // Number of ICs to be written to
|
|
uint8_t tx_cmd[2], // The command to be transmitted
|
|
uint8_t data[] // Payload Data
|
|
)
|
|
{
|
|
const uint8_t BYTES_IN_REG = 6;
|
|
const uint8_t CMD_LEN = 4 + (8 * total_ic);
|
|
uint8_t *cmd;
|
|
uint16_t data_pec;
|
|
uint16_t cmd_pec;
|
|
uint8_t cmd_index;
|
|
|
|
cmd = (uint8_t *)malloc(CMD_LEN * sizeof(uint8_t));
|
|
cmd[0] = tx_cmd[0];
|
|
cmd[1] = tx_cmd[1];
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cmd_index = 4;
|
|
for (uint8_t current_ic = total_ic; current_ic > 0; current_ic--) // Executes for each ADBMS181x, this loops starts with the last IC on the stack.
|
|
{ // The first configuration written is received by the last IC in the daisy chain
|
|
for (uint8_t current_byte = 0; current_byte < BYTES_IN_REG; current_byte++)
|
|
{
|
|
cmd[cmd_index] = data[((current_ic - 1) * 6) + current_byte];
|
|
cmd_index = cmd_index + 1;
|
|
}
|
|
|
|
data_pec = (uint16_t)pec15_calc(BYTES_IN_REG, &data[(current_ic - 1) * 6]); // Calculating the PEC for each ICs configuration register data
|
|
cmd[cmd_index] = (uint8_t)(data_pec >> 8);
|
|
cmd[cmd_index + 1] = (uint8_t)data_pec;
|
|
cmd_index = cmd_index + 2;
|
|
}
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_array(CMD_LEN, cmd);
|
|
cs_high(CS_PIN);
|
|
|
|
free(cmd);
|
|
}
|
|
|
|
/* Generic function to write 68xx commands and read data. Function calculated PEC for tx_cmd data */
|
|
int8_t read_68(uint8_t total_ic, // Number of ICs in the system
|
|
uint8_t tx_cmd[2], // The command to be transmitted
|
|
uint8_t *rx_data // Data to be read
|
|
)
|
|
{
|
|
const uint8_t BYTES_IN_REG = 8;
|
|
uint8_t cmd[4];
|
|
uint8_t data[256];
|
|
int8_t pec_error = 0;
|
|
uint16_t cmd_pec;
|
|
uint16_t data_pec;
|
|
uint16_t received_pec;
|
|
|
|
cmd[0] = tx_cmd[0];
|
|
cmd[1] = tx_cmd[1];
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_read(cmd, 4, data, (BYTES_IN_REG * total_ic)); // Transmits the command and reads the configuration data of all ICs on the daisy chain into rx_data[] array
|
|
cs_high(CS_PIN);
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++) // Executes for each ADBMS181x in the daisy chain and packs the data
|
|
{ // into the rx_data array as well as check the received data for any bit errors
|
|
for (uint8_t current_byte = 0; current_byte < BYTES_IN_REG; current_byte++)
|
|
{
|
|
rx_data[(current_ic * 8) + current_byte] = data[current_byte + (current_ic * BYTES_IN_REG)];
|
|
}
|
|
|
|
received_pec = (rx_data[(current_ic * 8) + 6] << 8) + rx_data[(current_ic * 8) + 7];
|
|
data_pec = pec15_calc(6, &rx_data[current_ic * 8]);
|
|
|
|
if (received_pec != data_pec)
|
|
{
|
|
pec_error = -1;
|
|
}
|
|
}
|
|
|
|
return (pec_error);
|
|
}
|
|
|
|
/* Calculates and returns the CRC15 */
|
|
uint16_t pec15_calc(uint8_t len, // Number of bytes that will be used to calculate a PEC
|
|
uint8_t *data // Array of data that will be used to calculate a PEC
|
|
)
|
|
{
|
|
uint16_t remainder, addr;
|
|
remainder = 16; // initialize the PEC
|
|
|
|
for (uint8_t i = 0; i < len; i++) // loops for each byte in data array
|
|
{
|
|
addr = ((remainder >> 7) ^ data[i]) & 0xff; // calculate PEC table address
|
|
#ifdef MBED
|
|
remainder = (remainder << 8) ^ crc15Table[addr];
|
|
#else
|
|
remainder = (remainder << 8) ^ pgm_read_word_near(crc15Table + addr);
|
|
#endif
|
|
}
|
|
|
|
return (remainder * 2); // The CRC15 has a 0 in the LSB so the remainder must be multiplied by 2
|
|
}
|
|
|
|
/* Write the ADBMS181x CFGRA */
|
|
void ADBMS181x_wrcfg(uint8_t total_ic, // The number of ICs being written to
|
|
cell_asic ic[] // A two dimensional array of the configuration data that will be written
|
|
)
|
|
{
|
|
uint8_t cmd[2] = {0x00, 0x01};
|
|
uint8_t write_buffer[256];
|
|
uint8_t write_count = 0;
|
|
uint8_t c_ic = 0;
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (uint8_t data = 0; data < 6; data++)
|
|
{
|
|
write_buffer[write_count] = ic[c_ic].config.tx_data[data];
|
|
write_count++;
|
|
}
|
|
}
|
|
write_68(total_ic, cmd, write_buffer);
|
|
}
|
|
|
|
/* Write the ADBMS181x CFGRB */
|
|
void ADBMS181x_wrcfgb(uint8_t total_ic, // The number of ICs being written to
|
|
cell_asic ic[] // A two dimensional array of the configuration data that will be written
|
|
)
|
|
{
|
|
uint8_t cmd[2] = {0x00, 0x24};
|
|
uint8_t write_buffer[256];
|
|
uint8_t write_count = 0;
|
|
uint8_t c_ic = 0;
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (uint8_t data = 0; data < 6; data++)
|
|
{
|
|
write_buffer[write_count] = ic[c_ic].configb.tx_data[data];
|
|
write_count++;
|
|
}
|
|
}
|
|
write_68(total_ic, cmd, write_buffer);
|
|
}
|
|
|
|
/* Read the ADBMS181x CFGA */
|
|
int8_t ADBMS181x_rdcfg(uint8_t total_ic, // Number of ICs in the system
|
|
cell_asic ic[] // A two dimensional array that the function stores the read configuration data.
|
|
)
|
|
{
|
|
uint8_t cmd[2] = {0x00, 0x02};
|
|
uint8_t read_buffer[256];
|
|
int8_t pec_error = 0;
|
|
uint16_t data_pec;
|
|
uint16_t calc_pec;
|
|
uint8_t c_ic = 0;
|
|
|
|
pec_error = read_68(total_ic, cmd, read_buffer);
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (int byte = 0; byte < 8; byte++)
|
|
{
|
|
ic[c_ic].config.rx_data[byte] = read_buffer[byte + (8 * current_ic)];
|
|
}
|
|
|
|
calc_pec = pec15_calc(6, &read_buffer[8 * current_ic]);
|
|
data_pec = read_buffer[7 + (8 * current_ic)] | (read_buffer[6 + (8 * current_ic)] << 8);
|
|
if (calc_pec != data_pec)
|
|
{
|
|
ic[c_ic].config.rx_pec_match = 1;
|
|
}
|
|
else
|
|
ic[c_ic].config.rx_pec_match = 0;
|
|
}
|
|
ADBMS181x_check_pec(total_ic, CFGR, ic);
|
|
|
|
return (pec_error);
|
|
}
|
|
|
|
/* Reads the ADBMS181x CFGB */
|
|
int8_t ADBMS181x_rdcfgb(uint8_t total_ic, // Number of ICs in the system
|
|
cell_asic ic[] // A two dimensional array that the function stores the read configuration data.
|
|
)
|
|
{
|
|
uint8_t cmd[2] = {0x00, 0x26};
|
|
uint8_t read_buffer[256];
|
|
int8_t pec_error = 0;
|
|
uint16_t data_pec;
|
|
uint16_t calc_pec;
|
|
uint8_t c_ic = 0;
|
|
|
|
pec_error = read_68(total_ic, cmd, read_buffer);
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (int byte = 0; byte < 8; byte++)
|
|
{
|
|
ic[c_ic].configb.rx_data[byte] = read_buffer[byte + (8 * current_ic)];
|
|
}
|
|
|
|
calc_pec = pec15_calc(6, &read_buffer[8 * current_ic]);
|
|
data_pec = read_buffer[7 + (8 * current_ic)] | (read_buffer[6 + (8 * current_ic)] << 8);
|
|
if (calc_pec != data_pec)
|
|
{
|
|
ic[c_ic].configb.rx_pec_match = 1;
|
|
}
|
|
else
|
|
ic[c_ic].configb.rx_pec_match = 0;
|
|
}
|
|
ADBMS181x_check_pec(total_ic, CFGRB, ic);
|
|
|
|
return (pec_error);
|
|
}
|
|
|
|
/* Starts ADC conversion for cell voltage */
|
|
void ADBMS181x_adcv(uint8_t MD, // ADC Mode
|
|
uint8_t DCP, // Discharge Permit
|
|
uint8_t CH // Cell Channels to be measured
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x02;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + 0x60 + (DCP << 4) + CH;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Start ADC Conversion for GPIO and Vref2 */
|
|
void ADBMS181x_adax(uint8_t MD, // ADC Mode
|
|
uint8_t CHG // GPIO Channels to be measured
|
|
)
|
|
{
|
|
uint8_t cmd[4];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + 0x60 + CHG;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Start ADC Conversion for Status */
|
|
void ADBMS181x_adstat(uint8_t MD, // ADC Mode
|
|
uint8_t CHST // Stat Channels to be measured
|
|
)
|
|
{
|
|
uint8_t cmd[4];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + 0x68 + CHST;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Starts cell voltage and SOC conversion */
|
|
void ADBMS181x_adcvsc(uint8_t MD, // ADC Mode
|
|
uint8_t DCP // Discharge Permit
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits | 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits | 0x60 | (DCP << 4) | 0x07;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Starts cell voltage and GPIO 1&2 conversion */
|
|
void ADBMS181x_adcvax(uint8_t MD, // ADC Mode
|
|
uint8_t DCP // Discharge Permit
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits | 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits | ((DCP & 0x01) << 4) + 0x6F;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/*
|
|
Reads and parses the ADBMS181x cell voltage registers.
|
|
The function is used to read the parsed Cell voltages codes of the ADBMS181x.
|
|
This function will send the requested read commands parse the data
|
|
and store the cell voltages in c_codes variable.
|
|
*/
|
|
uint8_t ADBMS181x_rdcv(uint8_t reg, // Controls which cell voltage register is read back.
|
|
uint8_t total_ic, // The number of ICs in the system
|
|
cell_asic *ic // Array of the parsed cell codes
|
|
)
|
|
{
|
|
int8_t pec_error = 0;
|
|
uint8_t *cell_data;
|
|
uint8_t c_ic = 0;
|
|
cell_data = (uint8_t *)malloc((NUM_RX_BYT * total_ic) * sizeof(uint8_t));
|
|
|
|
if (reg == 0)
|
|
{
|
|
for (uint8_t cell_reg = 1; cell_reg < ic[0].ic_reg.num_cv_reg + 1; cell_reg++) // Executes once for each of the ADBMS181x cell voltage registers
|
|
{
|
|
ADBMS181x_rdcv_reg(cell_reg, total_ic, cell_data);
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
pec_error = pec_error + parse_cells(current_ic, cell_reg, cell_data,
|
|
&ic[c_ic].cells.c_codes[0],
|
|
&ic[c_ic].cells.pec_match[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
ADBMS181x_rdcv_reg(reg, total_ic, cell_data);
|
|
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
pec_error = pec_error + parse_cells(current_ic, reg, &cell_data[8 * c_ic],
|
|
&ic[c_ic].cells.c_codes[0],
|
|
&ic[c_ic].cells.pec_match[0]);
|
|
}
|
|
}
|
|
ADBMS181x_check_pec(total_ic, CELL, ic);
|
|
free(cell_data);
|
|
|
|
return (pec_error);
|
|
}
|
|
|
|
/*
|
|
The function is used to read the parsed GPIO codes of the ADBMS181x.
|
|
This function will send the requested read commands parse the data
|
|
and store the gpio voltages in a_codes variable.
|
|
*/
|
|
int8_t ADBMS181x_rdaux(uint8_t reg, // Determines which GPIO voltage register is read back.
|
|
uint8_t total_ic, // The number of ICs in the system
|
|
cell_asic *ic // A two dimensional array of the gpio voltage codes.
|
|
)
|
|
{
|
|
uint8_t *data;
|
|
int8_t pec_error = 0;
|
|
uint8_t c_ic = 0;
|
|
data = (uint8_t *)malloc((NUM_RX_BYT * total_ic) * sizeof(uint8_t));
|
|
|
|
if (reg == 0)
|
|
{
|
|
for (uint8_t gpio_reg = 1; gpio_reg < ic[0].ic_reg.num_gpio_reg + 1; gpio_reg++) // Executes once for each of the ADBMS181x aux voltage registers
|
|
{
|
|
ADBMS181x_rdaux_reg(gpio_reg, total_ic, data); // Reads the raw auxiliary register data into the data[] array
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
pec_error = parse_cells(current_ic, gpio_reg, data,
|
|
&ic[c_ic].aux.a_codes[0],
|
|
&ic[c_ic].aux.pec_match[0]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ADBMS181x_rdaux_reg(reg, total_ic, data);
|
|
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
pec_error = parse_cells(current_ic, reg, data,
|
|
&ic[c_ic].aux.a_codes[0],
|
|
&ic[c_ic].aux.pec_match[0]);
|
|
}
|
|
}
|
|
ADBMS181x_check_pec(total_ic, AUX, ic);
|
|
free(data);
|
|
|
|
return (pec_error);
|
|
}
|
|
|
|
/*
|
|
Reads and parses the ADBMS181x stat registers.
|
|
The function is used to read the parsed Stat codes of the ADBMS181x.
|
|
This function will send the requested read commands parse the data
|
|
and store the gpio voltages in stat_codes variable.
|
|
*/
|
|
int8_t ADBMS181x_rdstat(uint8_t reg, // Determines which Stat register is read back.
|
|
uint8_t total_ic, // The number of ICs in the system
|
|
cell_asic *ic // A two dimensional array of the stat codes.
|
|
)
|
|
|
|
{
|
|
const uint8_t BYT_IN_REG = 6;
|
|
const uint8_t STAT_IN_REG = 3;
|
|
uint8_t *data;
|
|
uint8_t data_counter = 0;
|
|
int8_t pec_error = 0;
|
|
uint16_t parsed_stat;
|
|
uint16_t received_pec;
|
|
uint16_t data_pec;
|
|
uint8_t c_ic = 0;
|
|
|
|
data = (uint8_t *)malloc((12 * total_ic) * sizeof(uint8_t));
|
|
|
|
if (reg == 0)
|
|
{
|
|
for (uint8_t stat_reg = 1; stat_reg < 3; stat_reg++) // Executes once for each of the ADBMS181x stat voltage registers
|
|
{
|
|
data_counter = 0;
|
|
ADBMS181x_rdstat_reg(stat_reg, total_ic, data); // Reads the raw status register data into the data[] array
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++) // Executes for every ADBMS181x in the daisy chain
|
|
{ // current_ic is used as the IC counter
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
if (stat_reg == 1)
|
|
{
|
|
for (uint8_t current_stat = 0; current_stat < STAT_IN_REG; current_stat++) // This loop parses the read back data into Status registers,
|
|
{ // it loops once for each of the 3 stat codes in the register
|
|
parsed_stat = data[data_counter] + (data[data_counter + 1] << 8); // Each stat codes is received as two bytes and is combined to create the parsed status code
|
|
ic[c_ic].stat.stat_codes[current_stat] = parsed_stat;
|
|
data_counter = data_counter + 2; // Because stat codes are two bytes the data counter
|
|
}
|
|
}
|
|
else if (stat_reg == 2)
|
|
{
|
|
parsed_stat = data[data_counter] + (data[data_counter + 1] << 8); // Each stat is received as two bytes and is combined to create the parsed status code
|
|
data_counter = data_counter + 2;
|
|
ic[c_ic].stat.stat_codes[3] = parsed_stat;
|
|
ic[c_ic].stat.flags[0] = data[data_counter++];
|
|
ic[c_ic].stat.flags[1] = data[data_counter++];
|
|
ic[c_ic].stat.flags[2] = data[data_counter++];
|
|
ic[c_ic].stat.mux_fail[0] = (data[data_counter] & 0x02) >> 1;
|
|
ic[c_ic].stat.thsd[0] = data[data_counter++] & 0x01;
|
|
}
|
|
|
|
received_pec = (data[data_counter] << 8) + data[data_counter + 1]; // The received PEC for the current_ic is transmitted as the 7th and 8th
|
|
// after the 6 status data bytes
|
|
data_pec = pec15_calc(BYT_IN_REG, &data[current_ic * NUM_RX_BYT]);
|
|
|
|
if (received_pec != data_pec)
|
|
{
|
|
pec_error = -1; // The pec_error variable is simply set negative if any PEC errors
|
|
ic[c_ic].stat.pec_match[stat_reg - 1] = 1; // are detected in the received serial data
|
|
}
|
|
else
|
|
{
|
|
ic[c_ic].stat.pec_match[stat_reg - 1] = 0;
|
|
}
|
|
|
|
data_counter = data_counter + 2; // Because the transmitted PEC code is 2 bytes long the data_counter
|
|
// must be incremented by 2 bytes to point to the next ICs status data
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ADBMS181x_rdstat_reg(reg, total_ic, data);
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++) // Executes for every ADBMS181x in the daisy chain
|
|
{ // current_ic is used as an IC counter
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
if (reg == 1)
|
|
{
|
|
for (uint8_t current_stat = 0; current_stat < STAT_IN_REG; current_stat++) // This loop parses the read back data into Status voltages, it
|
|
{ // loops once for each of the 3 stat codes in the register
|
|
|
|
parsed_stat = data[data_counter] + (data[data_counter + 1] << 8); // Each stat codes is received as two bytes and is combined to
|
|
// create the parsed stat code
|
|
|
|
ic[c_ic].stat.stat_codes[current_stat] = parsed_stat;
|
|
data_counter = data_counter + 2; // Because stat codes are two bytes the data counter
|
|
// must increment by two for each parsed stat code
|
|
}
|
|
}
|
|
else if (reg == 2)
|
|
{
|
|
parsed_stat = data[data_counter++] + (data[data_counter++] << 8); // Each stat codes is received as two bytes and is combined to
|
|
ic[c_ic].stat.stat_codes[3] = parsed_stat;
|
|
ic[c_ic].stat.flags[0] = data[data_counter++];
|
|
ic[c_ic].stat.flags[1] = data[data_counter++];
|
|
ic[c_ic].stat.flags[2] = data[data_counter++];
|
|
ic[c_ic].stat.mux_fail[0] = (data[data_counter] & 0x02) >> 1;
|
|
ic[c_ic].stat.thsd[0] = data[data_counter++] & 0x01;
|
|
}
|
|
|
|
received_pec = (data[data_counter] << 8) + data[data_counter + 1]; // The received PEC for the current_ic is transmitted as the 7th and 8th
|
|
// after the 6 status data bytes
|
|
data_pec = pec15_calc(BYT_IN_REG, &data[current_ic * NUM_RX_BYT]);
|
|
if (received_pec != data_pec)
|
|
{
|
|
pec_error = -1; // The pec_error variable is simply set negative if any PEC errors
|
|
ic[c_ic].stat.pec_match[reg - 1] = 1;
|
|
}
|
|
|
|
data_counter = data_counter + 2;
|
|
}
|
|
}
|
|
ADBMS181x_check_pec(total_ic, STAT, ic);
|
|
|
|
free(data);
|
|
|
|
return (pec_error);
|
|
}
|
|
|
|
/* Writes the command and reads the raw cell voltage register data */
|
|
void ADBMS181x_rdcv_reg(uint8_t reg, // Determines which cell voltage register is read back
|
|
uint8_t total_ic, // the number of ICs in the
|
|
uint8_t *data // An array of the unparsed cell codes
|
|
)
|
|
{
|
|
const uint8_t REG_LEN = 8; // Number of bytes in each ICs register + 2 bytes for the PEC
|
|
uint8_t cmd[4];
|
|
uint16_t cmd_pec;
|
|
|
|
if (reg == 1) // 1: RDCVA
|
|
{
|
|
cmd[1] = 0x04;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 2) // 2: RDCVB
|
|
{
|
|
cmd[1] = 0x06;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 3) // 3: RDCVC
|
|
{
|
|
cmd[1] = 0x08;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 4) // 4: RDCVD
|
|
{
|
|
cmd[1] = 0x0A;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 5) // 4: RDCVE
|
|
{
|
|
cmd[1] = 0x09;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 6) // 4: RDCVF
|
|
{
|
|
cmd[1] = 0x0B;
|
|
cmd[0] = 0x00;
|
|
}
|
|
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_read(cmd, 4, data, (REG_LEN * total_ic));
|
|
cs_high(CS_PIN);
|
|
}
|
|
|
|
/*
|
|
The function reads a single GPIO voltage register and stores the read data
|
|
in the *data point as a byte array. This function is rarely used outside of
|
|
the ADBMS181x_rdaux() command.
|
|
*/
|
|
void ADBMS181x_rdaux_reg(uint8_t reg, // Determines which GPIO voltage register is read back
|
|
uint8_t total_ic, // The number of ICs in the system
|
|
uint8_t *data // Array of the unparsed auxiliary codes
|
|
)
|
|
{
|
|
const uint8_t REG_LEN = 8; // Number of bytes in the register + 2 bytes for the PEC
|
|
uint8_t cmd[4];
|
|
uint16_t cmd_pec;
|
|
|
|
if (reg == 1) // Read back auxiliary group A
|
|
{
|
|
cmd[1] = 0x0C;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 2) // Read back auxiliary group B
|
|
{
|
|
cmd[1] = 0x0E;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 3) // Read back auxiliary group C
|
|
{
|
|
cmd[1] = 0x0D;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 4) // Read back auxiliary group D
|
|
{
|
|
cmd[1] = 0x0F;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else // Read back auxiliary group A
|
|
{
|
|
cmd[1] = 0x0C;
|
|
cmd[0] = 0x00;
|
|
}
|
|
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_read(cmd, 4, data, (REG_LEN * total_ic));
|
|
cs_high(CS_PIN);
|
|
}
|
|
|
|
/*
|
|
The function reads a single stat register and stores the read data
|
|
in the *data point as a byte array. This function is rarely used outside of
|
|
the ADBMS181x_rdstat() command.
|
|
*/
|
|
void ADBMS181x_rdstat_reg(uint8_t reg, // Determines which stat register is read back
|
|
uint8_t total_ic, // The number of ICs in the system
|
|
uint8_t *data // Array of the unparsed stat codes
|
|
)
|
|
{
|
|
const uint8_t REG_LEN = 8; // number of bytes in the register + 2 bytes for the PEC
|
|
uint8_t cmd[4];
|
|
uint16_t cmd_pec;
|
|
|
|
if (reg == 1) // Read back status group A
|
|
{
|
|
cmd[1] = 0x10;
|
|
cmd[0] = 0x00;
|
|
}
|
|
else if (reg == 2) // Read back status group B
|
|
{
|
|
cmd[1] = 0x12;
|
|
cmd[0] = 0x00;
|
|
}
|
|
|
|
else // Read back status group A
|
|
{
|
|
cmd[1] = 0x10;
|
|
cmd[0] = 0x00;
|
|
}
|
|
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_read(cmd, 4, data, (REG_LEN * total_ic));
|
|
cs_high(CS_PIN);
|
|
}
|
|
|
|
/* Helper function that parses voltage measurement registers */
|
|
int8_t parse_cells(uint8_t current_ic, // Current IC
|
|
uint8_t cell_reg, // Type of register
|
|
uint8_t cell_data[], // Unparsed data
|
|
uint16_t *cell_codes, // Parsed data
|
|
uint8_t *ic_pec // PEC error
|
|
)
|
|
{
|
|
const uint8_t BYT_IN_REG = 6;
|
|
const uint8_t CELL_IN_REG = 3;
|
|
int8_t pec_error = 0;
|
|
uint16_t parsed_cell;
|
|
uint16_t received_pec;
|
|
uint16_t data_pec;
|
|
uint8_t data_counter = current_ic * NUM_RX_BYT; // data counter
|
|
|
|
for (uint8_t current_cell = 0; current_cell < CELL_IN_REG; current_cell++) // This loop parses the read back data into the register codes, it
|
|
{ // loops once for each of the 3 codes in the register
|
|
|
|
parsed_cell = cell_data[data_counter] + (cell_data[data_counter + 1] << 8); // Each code is received as two bytes and is combined to
|
|
// create the parsed code
|
|
cell_codes[current_cell + ((cell_reg - 1) * CELL_IN_REG)] = parsed_cell;
|
|
|
|
data_counter = data_counter + 2; // Because the codes are two bytes, the data counter
|
|
// must increment by two for each parsed code
|
|
}
|
|
received_pec = (cell_data[data_counter] << 8) | cell_data[data_counter + 1]; // The received PEC for the current_ic is transmitted as the 7th and 8th
|
|
// after the 6 cell voltage data bytes
|
|
data_pec = pec15_calc(BYT_IN_REG, &cell_data[(current_ic)*NUM_RX_BYT]);
|
|
|
|
if (received_pec != data_pec)
|
|
{
|
|
pec_error = 1; // The pec_error variable is simply set negative if any PEC errors
|
|
ic_pec[cell_reg - 1] = 1;
|
|
}
|
|
else
|
|
{
|
|
ic_pec[cell_reg - 1] = 0;
|
|
}
|
|
data_counter = data_counter + 2;
|
|
|
|
return (pec_error);
|
|
}
|
|
|
|
/* Sends the poll ADC command */
|
|
uint8_t ADBMS181x_pladc()
|
|
{
|
|
uint8_t cmd[4];
|
|
uint8_t adc_state = 0xFF;
|
|
uint16_t cmd_pec;
|
|
|
|
cmd[0] = 0x07;
|
|
cmd[1] = 0x14;
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_array(4, cmd);
|
|
adc_state = spi_read_byte(0xFF);
|
|
cs_high(CS_PIN);
|
|
|
|
return (adc_state);
|
|
}
|
|
|
|
/* This function will block operation until the ADC has finished it's conversion */
|
|
uint32_t ADBMS181x_pollAdc()
|
|
{
|
|
uint32_t counter = 0;
|
|
uint8_t finished = 0;
|
|
uint8_t current_time = 0;
|
|
uint8_t cmd[4];
|
|
uint16_t cmd_pec;
|
|
|
|
cmd[0] = 0x07;
|
|
cmd[1] = 0x14;
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_array(4, cmd);
|
|
while ((counter < 200000) && (finished == 0))
|
|
{
|
|
current_time = spi_read_byte(0xff);
|
|
if (current_time > 0)
|
|
{
|
|
finished = 1;
|
|
}
|
|
else
|
|
{
|
|
counter = counter + 10;
|
|
}
|
|
}
|
|
cs_high(CS_PIN);
|
|
|
|
return (counter);
|
|
}
|
|
|
|
/*
|
|
The command clears the cell voltage registers and initializes
|
|
all values to 1. The register will read back hexadecimal 0xFF
|
|
after the command is sent.
|
|
*/
|
|
void ADBMS181x_clrcell()
|
|
{
|
|
uint8_t cmd[2] = {0x07, 0x11};
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/*
|
|
The command clears the Auxiliary registers and initializes
|
|
all values to 1. The register will read back hexadecimal 0xFF
|
|
after the command is sent.
|
|
*/
|
|
void ADBMS181x_clraux()
|
|
{
|
|
uint8_t cmd[2] = {0x07, 0x12};
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/*
|
|
The command clears the Stat registers and initializes
|
|
all values to 1. The register will read back hexadecimal 0xFF
|
|
after the command is sent.
|
|
|
|
*/
|
|
void ADBMS181x_clrstat()
|
|
{
|
|
uint8_t cmd[2] = {0x07, 0x13};
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Starts the Mux Decoder diagnostic self test */
|
|
void ADBMS181x_diagn()
|
|
{
|
|
uint8_t cmd[2] = {0x07, 0x15};
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Starts cell voltage self test conversion */
|
|
void ADBMS181x_cvst(uint8_t MD, // ADC Mode
|
|
uint8_t ST // Self Test
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x02;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + ((ST) << 5) + 0x07;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Start an Auxiliary Register Self Test Conversion */
|
|
void ADBMS181x_axst(uint8_t MD, // ADC Mode
|
|
uint8_t ST // Self Test
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + ((ST & 0x03) << 5) + 0x07;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Start a Status Register Self Test Conversion */
|
|
void ADBMS181x_statst(uint8_t MD, // ADC Mode
|
|
uint8_t ST // Self Test
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + ((ST & 0x03) << 5) + 0x0F;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Starts cell voltage overlap conversion */
|
|
void ADBMS181x_adol(uint8_t MD, // ADC Mode
|
|
uint8_t DCP // Discharge Permit
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x02;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + (DCP << 4) + 0x01;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Start an GPIO Redundancy test */
|
|
void ADBMS181x_adaxd(uint8_t MD, // ADC Mode
|
|
uint8_t CHG // GPIO Channels to be measured
|
|
)
|
|
{
|
|
uint8_t cmd[4];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + CHG;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Start a Status register redundancy test Conversion */
|
|
void ADBMS181x_adstatd(uint8_t MD, // ADC Mode
|
|
uint8_t CHST // Stat Channels to be measured
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + 0x08 + CHST;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Runs the Digital Filter Self Test */
|
|
int16_t ADBMS181x_run_cell_adc_st(uint8_t adc_reg, // Type of register
|
|
uint8_t total_ic, // Number of ICs in the daisy chain
|
|
cell_asic *ic, // A two dimensional array that will store the data
|
|
uint8_t md, // ADC Mode
|
|
bool adcopt // ADCOPT bit in the configuration register
|
|
)
|
|
{
|
|
int16_t error = 0;
|
|
uint16_t expected_result = 0;
|
|
|
|
for (int self_test = 1; self_test < 3; self_test++)
|
|
{
|
|
expected_result = ADBMS181x_st_lookup(md, self_test, adcopt);
|
|
wakeup_idle(total_ic);
|
|
|
|
switch (adc_reg)
|
|
{
|
|
case CELL:
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_clrcell();
|
|
ADBMS181x_cvst(md, self_test);
|
|
ADBMS181x_pollAdc();
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdcv(0, total_ic, ic);
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int channel = 0; channel < ic[cic].ic_reg.cell_channels; channel++)
|
|
{
|
|
|
|
if (ic[cic].cells.c_codes[channel] != expected_result)
|
|
{
|
|
error = error + 1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AUX:
|
|
error = 0;
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_clraux();
|
|
ADBMS181x_axst(md, self_test);
|
|
ADBMS181x_pollAdc();
|
|
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_rdaux(0, total_ic, ic);
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int channel = 0; channel < ic[cic].ic_reg.aux_channels; channel++)
|
|
{
|
|
|
|
if (ic[cic].aux.a_codes[channel] != expected_result)
|
|
{
|
|
error = error + 1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case STAT:
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_clrstat();
|
|
ADBMS181x_statst(md, self_test);
|
|
ADBMS181x_pollAdc();
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdstat(0, total_ic, ic);
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int channel = 0; channel < ic[cic].ic_reg.stat_channels; channel++)
|
|
{
|
|
if (ic[cic].stat.stat_codes[channel] != expected_result)
|
|
{
|
|
error = error + 1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
error = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
/* Runs the ADC overlap test for the IC */
|
|
uint16_t ADBMS181x_run_adc_overlap(uint8_t total_ic, // Number of ICs in the daisy chain
|
|
cell_asic *ic // A two dimensional array that will store the data
|
|
)
|
|
{
|
|
uint16_t error = 0;
|
|
int32_t measure_delta = 0;
|
|
int16_t failure_pos_limit = 20;
|
|
int16_t failure_neg_limit = -20;
|
|
int32_t a, b;
|
|
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_adol(MD_7KHZ_3KHZ, DCP_DISABLED);
|
|
ADBMS181x_pollAdc();
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdcv(0, total_ic, ic);
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
measure_delta = (int32_t)ic[cic].cells.c_codes[6] - (int32_t)ic[cic].cells.c_codes[7];
|
|
|
|
if ((measure_delta > failure_pos_limit) || (measure_delta < failure_neg_limit))
|
|
{
|
|
error = error | (1 << (cic - 1));
|
|
}
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
/* Runs the redundancy self test */
|
|
int16_t ADBMS181x_run_adc_redundancy_st(uint8_t adc_mode, // ADC Mode
|
|
uint8_t adc_reg, // Type of register
|
|
uint8_t total_ic, // Number of ICs in the daisy chain
|
|
cell_asic *ic // A two dimensional array that will store the data
|
|
)
|
|
{
|
|
int16_t error = 0;
|
|
for (int self_test = 1; self_test < 3; self_test++)
|
|
{
|
|
wakeup_idle(total_ic);
|
|
switch (adc_reg)
|
|
{
|
|
case AUX:
|
|
ADBMS181x_clraux();
|
|
ADBMS181x_adaxd(adc_mode, AUX_CH_ALL);
|
|
ADBMS181x_pollAdc();
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdaux(0, total_ic, ic);
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int channel = 0; channel < ic[cic].ic_reg.aux_channels; channel++)
|
|
{
|
|
if (ic[cic].aux.a_codes[channel] >= 65280)
|
|
{
|
|
error = error + 1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case STAT:
|
|
ADBMS181x_clrstat();
|
|
ADBMS181x_adstatd(adc_mode, STAT_CH_ALL);
|
|
ADBMS181x_pollAdc();
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdstat(0, total_ic, ic);
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int channel = 0; channel < ic[cic].ic_reg.stat_channels; channel++)
|
|
{
|
|
if (ic[cic].stat.stat_codes[channel] >= 65280)
|
|
{
|
|
error = error + 1;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
error = -1;
|
|
break;
|
|
}
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
/* Looks up the result pattern for digital filter self test */
|
|
uint16_t ADBMS181x_st_lookup(uint8_t MD, // ADC Mode
|
|
uint8_t ST, // Self Test
|
|
bool adcopt // ADCOPT bit in the configuration register
|
|
)
|
|
{
|
|
uint16_t test_pattern = 0;
|
|
|
|
if (MD == 1)
|
|
{
|
|
if (adcopt == false)
|
|
{
|
|
if (ST == 1)
|
|
{
|
|
test_pattern = 0x9565;
|
|
}
|
|
else
|
|
{
|
|
test_pattern = 0x6A9A;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ST == 1)
|
|
{
|
|
test_pattern = 0x9553;
|
|
}
|
|
else
|
|
{
|
|
test_pattern = 0x6AAC;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ST == 1)
|
|
{
|
|
test_pattern = 0x9555;
|
|
}
|
|
else
|
|
{
|
|
test_pattern = 0x6AAA;
|
|
}
|
|
}
|
|
return (test_pattern);
|
|
}
|
|
|
|
/* Start an open wire Conversion */
|
|
void ADBMS181x_adow(uint8_t MD, // ADC Mode
|
|
uint8_t PUP, // Pull up/Pull down current
|
|
uint8_t CH, // Channels
|
|
uint8_t DCP // Discharge Permit
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x02;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + 0x28 + (PUP << 6) + CH + (DCP << 4);
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Start GPIOs open wire ADC conversion */
|
|
void ADBMS181x_axow(uint8_t MD, // ADC Mode
|
|
uint8_t PUP // Pull up/Pull down current
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t md_bits;
|
|
|
|
md_bits = (MD & 0x02) >> 1;
|
|
cmd[0] = md_bits + 0x04;
|
|
md_bits = (MD & 0x01) << 7;
|
|
cmd[1] = md_bits + 0x10 + (PUP << 6); //+ CH;
|
|
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Runs the data sheet algorithm for open wire for single cell detection */
|
|
void ADBMS181x_run_openwire_single(uint8_t total_ic, // Number of ICs in the daisy chain
|
|
cell_asic ic[] // A two dimensional array that will store the data
|
|
)
|
|
{
|
|
uint16_t OPENWIRE_THRESHOLD = 4000;
|
|
const uint8_t N_CHANNELS = ic[0].ic_reg.cell_channels;
|
|
|
|
uint16_t pullUp[total_ic][N_CHANNELS];
|
|
uint16_t pullDwn[total_ic][N_CHANNELS];
|
|
int16_t openWire_delta[total_ic][N_CHANNELS];
|
|
|
|
int8_t error;
|
|
int8_t i;
|
|
uint32_t conv_time = 0;
|
|
|
|
wakeup_sleep(total_ic);
|
|
ADBMS181x_clrcell();
|
|
|
|
// Pull Ups
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_adow(MD_26HZ_2KHZ, PULL_UP_CURRENT, CELL_CH_ALL, DCP_DISABLED);
|
|
conv_time = ADBMS181x_pollAdc();
|
|
}
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdcv(0, total_ic, ic);
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int cell = 0; cell < N_CHANNELS; cell++)
|
|
{
|
|
pullUp[cic][cell] = ic[cic].cells.c_codes[cell];
|
|
}
|
|
}
|
|
|
|
// Pull Downs
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_adow(MD_26HZ_2KHZ, PULL_DOWN_CURRENT, CELL_CH_ALL, DCP_DISABLED);
|
|
conv_time = ADBMS181x_pollAdc();
|
|
}
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdcv(0, total_ic, ic);
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int cell = 0; cell < N_CHANNELS; cell++)
|
|
{
|
|
pullDwn[cic][cell] = ic[cic].cells.c_codes[cell];
|
|
}
|
|
}
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
ic[cic].system_open_wire = 0xFFFF;
|
|
|
|
for (int cell = 0; cell < N_CHANNELS; cell++)
|
|
{
|
|
if (pullDwn[cic][cell] < pullUp[cic][cell])
|
|
{
|
|
openWire_delta[cic][cell] = (pullUp[cic][cell] - pullDwn[cic][cell]);
|
|
}
|
|
else
|
|
{
|
|
openWire_delta[cic][cell] = 0;
|
|
}
|
|
|
|
if (openWire_delta[cic][cell] > OPENWIRE_THRESHOLD)
|
|
{
|
|
ic[cic].system_open_wire = cell + 1;
|
|
}
|
|
}
|
|
|
|
if (pullUp[cic][0] == 0)
|
|
{
|
|
ic[cic].system_open_wire = 0;
|
|
}
|
|
|
|
if (pullUp[cic][(N_CHANNELS - 1)] == 0) // checking the Pull up value of the top measured channel
|
|
{
|
|
ic[cic].system_open_wire = N_CHANNELS;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Runs the data sheet algorithm for open wire for multiple cell and two consecutive cells detection */
|
|
void ADBMS181x_run_openwire_multi(uint8_t total_ic, // Number of ICs in the daisy chain
|
|
cell_asic ic[] // A two dimensional array that will store the data
|
|
)
|
|
{
|
|
uint16_t OPENWIRE_THRESHOLD = 4000;
|
|
const uint8_t N_CHANNELS = ic[0].ic_reg.cell_channels;
|
|
|
|
uint16_t pullUp[total_ic][N_CHANNELS];
|
|
uint16_t pullDwn[total_ic][N_CHANNELS];
|
|
uint16_t openWire_delta[total_ic][N_CHANNELS];
|
|
|
|
int8_t error;
|
|
int8_t opencells[N_CHANNELS];
|
|
int8_t n = 0;
|
|
int8_t i, j, k;
|
|
uint32_t conv_time = 0;
|
|
|
|
wakeup_sleep(total_ic);
|
|
ADBMS181x_clrcell();
|
|
|
|
// Pull Ups
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_adow(MD_26HZ_2KHZ, PULL_UP_CURRENT, CELL_CH_ALL, DCP_DISABLED);
|
|
conv_time = ADBMS181x_pollAdc();
|
|
}
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdcv(0, total_ic, ic);
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int cell = 0; cell < N_CHANNELS; cell++)
|
|
{
|
|
pullUp[cic][cell] = ic[cic].cells.c_codes[cell];
|
|
}
|
|
}
|
|
|
|
// Pull Downs
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_adow(MD_26HZ_2KHZ, PULL_DOWN_CURRENT, CELL_CH_ALL, DCP_DISABLED);
|
|
conv_time = ADBMS181x_pollAdc();
|
|
}
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdcv(0, total_ic, ic);
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int cell = 0; cell < N_CHANNELS; cell++)
|
|
{
|
|
pullDwn[cic][cell] = ic[cic].cells.c_codes[cell];
|
|
}
|
|
}
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int cell = 0; cell < N_CHANNELS; cell++)
|
|
{
|
|
if (pullDwn[cic][cell] < pullUp[cic][cell])
|
|
{
|
|
openWire_delta[cic][cell] = (pullUp[cic][cell] - pullDwn[cic][cell]);
|
|
}
|
|
else
|
|
{
|
|
openWire_delta[cic][cell] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
n = 0;
|
|
|
|
Serial.print("IC:");
|
|
Serial.println(cic + 1, DEC);
|
|
|
|
for (int cell = 0; cell < N_CHANNELS; cell++)
|
|
{
|
|
|
|
if (openWire_delta[cic][cell] > OPENWIRE_THRESHOLD)
|
|
{
|
|
opencells[n] = cell + 1;
|
|
n++;
|
|
for (int j = cell; j < N_CHANNELS - 3; j++)
|
|
{
|
|
if (pullUp[cic][j + 2] == 0)
|
|
{
|
|
opencells[n] = j + 2;
|
|
n++;
|
|
}
|
|
}
|
|
if ((cell == N_CHANNELS - 4) && (pullDwn[cic][N_CHANNELS - 3] == 0))
|
|
{
|
|
opencells[n] = N_CHANNELS - 2;
|
|
n++;
|
|
}
|
|
}
|
|
}
|
|
if (pullDwn[cic][0] == 0)
|
|
{
|
|
opencells[n] = 0;
|
|
Serial.println("Cell 0 is Open and multiple open wires maybe possible.");
|
|
n++;
|
|
}
|
|
|
|
if (pullDwn[cic][N_CHANNELS - 1] == 0)
|
|
{
|
|
opencells[n] = N_CHANNELS;
|
|
n++;
|
|
}
|
|
|
|
if (pullDwn[cic][N_CHANNELS - 2] == 0)
|
|
{
|
|
opencells[n] = N_CHANNELS - 1;
|
|
n++;
|
|
}
|
|
|
|
// Removing repetitive elements
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
for (j = i + 1; j < n;)
|
|
{
|
|
if (opencells[i] == opencells[j])
|
|
{
|
|
for (k = j; k < n; k++)
|
|
opencells[k] = opencells[k + 1];
|
|
|
|
n--;
|
|
}
|
|
else
|
|
j++;
|
|
}
|
|
}
|
|
|
|
// Sorting open cell array
|
|
for (int i = 0; i < n; i++)
|
|
{
|
|
for (int j = 0; j < n - 1; j++)
|
|
{
|
|
if (opencells[j] > opencells[j + 1])
|
|
{
|
|
k = opencells[j];
|
|
opencells[j] = opencells[j + 1];
|
|
opencells[j + 1] = k;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checking the value of n
|
|
Serial.println("Number of Open wires:");
|
|
Serial.println(n);
|
|
|
|
// Printing open cell array
|
|
Serial.println("OPEN CELLS:");
|
|
if (n == 0)
|
|
{
|
|
Serial.println("No Open wires");
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
Serial.println(opencells[i]);
|
|
}
|
|
}
|
|
}
|
|
Serial.println("\n");
|
|
}
|
|
|
|
/* Runs open wire for GPIOs */
|
|
void ADBMS181x_run_gpio_openwire(uint8_t total_ic, // Number of ICs in the daisy chain
|
|
cell_asic ic[] // A two dimensional array that will store the data
|
|
)
|
|
{
|
|
uint16_t OPENWIRE_THRESHOLD = 150;
|
|
const uint8_t N_CHANNELS = ic[0].ic_reg.aux_channels + 1;
|
|
|
|
uint16_t aux_val[total_ic][N_CHANNELS];
|
|
uint16_t pDwn[total_ic][N_CHANNELS];
|
|
uint16_t ow_delta[total_ic][N_CHANNELS];
|
|
|
|
int8_t error;
|
|
int8_t i;
|
|
uint32_t conv_time = 0;
|
|
|
|
wakeup_sleep(total_ic);
|
|
ADBMS181x_clraux();
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_adax(MD_7KHZ_3KHZ, AUX_CH_ALL);
|
|
conv_time = ADBMS181x_pollAdc();
|
|
}
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdaux(0, total_ic, ic);
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int channel = 0; channel < N_CHANNELS; channel++)
|
|
{
|
|
aux_val[cic][channel] = ic[cic].aux.a_codes[channel];
|
|
}
|
|
}
|
|
ADBMS181x_clraux();
|
|
|
|
// pull downs
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
wakeup_idle(total_ic);
|
|
ADBMS181x_axow(MD_7KHZ_3KHZ, PULL_DOWN_CURRENT);
|
|
conv_time = ADBMS181x_pollAdc();
|
|
}
|
|
|
|
wakeup_idle(total_ic);
|
|
error = ADBMS181x_rdaux(0, total_ic, ic);
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
for (int channel = 0; channel < N_CHANNELS; channel++)
|
|
{
|
|
pDwn[cic][channel] = ic[cic].aux.a_codes[channel];
|
|
}
|
|
}
|
|
|
|
for (int cic = 0; cic < total_ic; cic++)
|
|
{
|
|
ic[cic].system_open_wire = 0xFFFF;
|
|
|
|
for (int channel = 0; channel < N_CHANNELS; channel++)
|
|
{
|
|
if (pDwn[cic][channel] > aux_val[cic][channel])
|
|
{
|
|
ow_delta[cic][channel] = (pDwn[cic][channel] - aux_val[cic][channel]);
|
|
}
|
|
else
|
|
{
|
|
ow_delta[cic][channel] = 0;
|
|
}
|
|
|
|
if (channel < 5)
|
|
{
|
|
if (ow_delta[cic][channel] > OPENWIRE_THRESHOLD)
|
|
{
|
|
ic[cic].system_open_wire = channel + 1;
|
|
}
|
|
}
|
|
else if (channel > 5)
|
|
{
|
|
if (ow_delta[cic][channel] > OPENWIRE_THRESHOLD)
|
|
{
|
|
ic[cic].system_open_wire = channel;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clears all of the DCC bits in the configuration registers */
|
|
void ADBMS181x_clear_discharge(uint8_t total_ic, // Number of ICs in the daisy chain
|
|
cell_asic *ic // A two dimensional array that will store the data
|
|
)
|
|
{
|
|
for (int i = 0; i < total_ic; i++)
|
|
{
|
|
ic[i].config.tx_data[4] = 0;
|
|
ic[i].config.tx_data[5] = ic[i].config.tx_data[5] & (0xF0);
|
|
ic[i].configb.tx_data[0] = ic[i].configb.tx_data[0] & (0x0F);
|
|
ic[i].configb.tx_data[1] = ic[i].configb.tx_data[1] & (0xF0);
|
|
}
|
|
}
|
|
|
|
/* Writes the pwm register */
|
|
void ADBMS181x_wrpwm(uint8_t total_ic, // Number of ICs in the daisy chain
|
|
uint8_t pwmReg, // The PWM Register to be written A or B
|
|
cell_asic ic[] // A two dimensional array that stores the data to be written
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t write_buffer[256];
|
|
uint8_t write_count = 0;
|
|
uint8_t c_ic = 0;
|
|
if (pwmReg == 0)
|
|
{
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x20;
|
|
}
|
|
else
|
|
{
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x1C;
|
|
}
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (uint8_t data = 0; data < 6; data++)
|
|
{
|
|
write_buffer[write_count] = ic[c_ic].pwm.tx_data[data];
|
|
write_count++;
|
|
}
|
|
}
|
|
write_68(total_ic, cmd, write_buffer);
|
|
}
|
|
|
|
/* Reads pwm registers of a ADBMS181x daisy chain */
|
|
int8_t ADBMS181x_rdpwm(uint8_t total_ic, // Number of ICs in the system
|
|
uint8_t pwmReg, // The PWM Register to be written A or B
|
|
cell_asic ic[] // A two dimensional array that will store the data
|
|
)
|
|
{
|
|
const uint8_t BYTES_IN_REG = 8;
|
|
uint8_t cmd[4];
|
|
uint8_t read_buffer[256];
|
|
int8_t pec_error = 0;
|
|
uint16_t data_pec;
|
|
uint16_t calc_pec;
|
|
uint8_t c_ic = 0;
|
|
|
|
if (pwmReg == 0)
|
|
{
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x22;
|
|
}
|
|
else
|
|
{
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x1E;
|
|
}
|
|
|
|
pec_error = read_68(total_ic, cmd, read_buffer);
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (int byte = 0; byte < 8; byte++)
|
|
{
|
|
ic[c_ic].pwm.rx_data[byte] = read_buffer[byte + (8 * current_ic)];
|
|
}
|
|
|
|
calc_pec = pec15_calc(6, &read_buffer[8 * current_ic]);
|
|
data_pec = read_buffer[7 + (8 * current_ic)] | (read_buffer[6 + (8 * current_ic)] << 8);
|
|
if (calc_pec != data_pec)
|
|
{
|
|
ic[c_ic].pwm.rx_pec_match = 1;
|
|
}
|
|
else
|
|
ic[c_ic].pwm.rx_pec_match = 0;
|
|
}
|
|
return (pec_error);
|
|
}
|
|
|
|
/* Write the ADBMS181x Sctrl register */
|
|
void ADBMS181x_wrsctrl(uint8_t total_ic, // Number of ICs in the daisy chain
|
|
uint8_t sctrl_reg, // The Sctrl Register to be written A or B
|
|
cell_asic *ic // A two dimensional array that stores the data to be written
|
|
)
|
|
{
|
|
uint8_t cmd[2];
|
|
uint8_t write_buffer[256];
|
|
uint8_t write_count = 0;
|
|
uint8_t c_ic = 0;
|
|
if (sctrl_reg == 0)
|
|
{
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x14;
|
|
}
|
|
else
|
|
{
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x1C;
|
|
}
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (uint8_t data = 0; data < 6; data++)
|
|
{
|
|
write_buffer[write_count] = ic[c_ic].sctrl.tx_data[data];
|
|
write_count++;
|
|
}
|
|
}
|
|
write_68(total_ic, cmd, write_buffer);
|
|
}
|
|
|
|
/* Reads sctrl registers of a ADBMS181x daisy chain */
|
|
int8_t ADBMS181x_rdsctrl(uint8_t total_ic, // Number of ICs in the daisy chain
|
|
uint8_t sctrl_reg, // The Sctrl Register to be written A or B
|
|
cell_asic *ic // A two dimensional array that the function stores the read data
|
|
)
|
|
{
|
|
uint8_t cmd[4];
|
|
uint8_t read_buffer[256];
|
|
int8_t pec_error = 0;
|
|
uint16_t data_pec;
|
|
uint16_t calc_pec;
|
|
uint8_t c_ic = 0;
|
|
|
|
if (sctrl_reg == 0)
|
|
{
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x16;
|
|
}
|
|
else
|
|
{
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x1E;
|
|
}
|
|
|
|
pec_error = read_68(total_ic, cmd, read_buffer);
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (int byte = 0; byte < 8; byte++)
|
|
{
|
|
ic[c_ic].sctrl.rx_data[byte] = read_buffer[byte + (8 * current_ic)];
|
|
}
|
|
|
|
calc_pec = pec15_calc(6, &read_buffer[8 * current_ic]);
|
|
data_pec = read_buffer[7 + (8 * current_ic)] | (read_buffer[6 + (8 * current_ic)] << 8);
|
|
if (calc_pec != data_pec)
|
|
{
|
|
ic[c_ic].sctrl.rx_pec_match = 1;
|
|
}
|
|
else
|
|
ic[c_ic].sctrl.rx_pec_match = 0;
|
|
}
|
|
return (pec_error);
|
|
}
|
|
|
|
/*
|
|
Start Sctrl data communication
|
|
This command will start the sctrl pulse communication over the spins
|
|
*/
|
|
void ADBMS181x_stsctrl()
|
|
{
|
|
uint8_t cmd[4];
|
|
uint16_t cmd_pec;
|
|
|
|
cmd[0] = 0x00;
|
|
cmd[1] = 0x19;
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_array(4, cmd);
|
|
cs_high(CS_PIN);
|
|
}
|
|
|
|
/*
|
|
The command clears the Sctrl registers and initializes
|
|
all values to 0. The register will read back hexadecimal 0x00
|
|
after the command is sent.
|
|
*/
|
|
void ADBMS181x_clrsctrl()
|
|
{
|
|
uint8_t cmd[2] = {0x00, 0x18};
|
|
cmd_68(cmd);
|
|
}
|
|
|
|
/* Writes the comm register */
|
|
void ADBMS181x_wrcomm(uint8_t total_ic, // The number of ICs being written to
|
|
cell_asic ic[] // A two dimensional array that stores the data to be written
|
|
)
|
|
{
|
|
uint8_t cmd[2] = {0x07, 0x21};
|
|
uint8_t write_buffer[256];
|
|
uint8_t write_count = 0;
|
|
uint8_t c_ic = 0;
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (uint8_t data = 0; data < 6; data++)
|
|
{
|
|
write_buffer[write_count] = ic[c_ic].com.tx_data[data];
|
|
write_count++;
|
|
}
|
|
}
|
|
write_68(total_ic, cmd, write_buffer);
|
|
}
|
|
|
|
/* Reads COMM registers of a ADBMS181x daisy chain */
|
|
int8_t ADBMS181x_rdcomm(uint8_t total_ic, // Number of ICs in the system
|
|
cell_asic ic[] // A two dimensional array that stores the read data
|
|
)
|
|
{
|
|
uint8_t cmd[2] = {0x07, 0x22};
|
|
uint8_t read_buffer[256];
|
|
int8_t pec_error = 0;
|
|
uint16_t data_pec;
|
|
uint16_t calc_pec;
|
|
uint8_t c_ic = 0;
|
|
|
|
pec_error = read_68(total_ic, cmd, read_buffer);
|
|
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
if (ic->isospi_reverse == false)
|
|
{
|
|
c_ic = current_ic;
|
|
}
|
|
else
|
|
{
|
|
c_ic = total_ic - current_ic - 1;
|
|
}
|
|
|
|
for (int byte = 0; byte < 8; byte++)
|
|
{
|
|
ic[c_ic].com.rx_data[byte] = read_buffer[byte + (8 * current_ic)];
|
|
}
|
|
|
|
calc_pec = pec15_calc(6, &read_buffer[8 * current_ic]);
|
|
data_pec = read_buffer[7 + (8 * current_ic)] | (read_buffer[6 + (8 * current_ic)] << 8);
|
|
if (calc_pec != data_pec)
|
|
{
|
|
ic[c_ic].com.rx_pec_match = 1;
|
|
}
|
|
else
|
|
ic[c_ic].com.rx_pec_match = 0;
|
|
}
|
|
|
|
return (pec_error);
|
|
}
|
|
|
|
/* Shifts data in COMM register out over ADBMS181x SPI/I2C port */
|
|
void ADBMS181x_stcomm(uint8_t len) // Length of data to be transmitted
|
|
{
|
|
uint8_t cmd[4];
|
|
uint16_t cmd_pec;
|
|
|
|
cmd[0] = 0x07;
|
|
cmd[1] = 0x23;
|
|
cmd_pec = pec15_calc(2, cmd);
|
|
cmd[2] = (uint8_t)(cmd_pec >> 8);
|
|
cmd[3] = (uint8_t)(cmd_pec);
|
|
|
|
cs_low(CS_PIN);
|
|
spi_write_array(4, cmd);
|
|
for (int i = 0; i < len * 3; i++)
|
|
{
|
|
spi_read_byte(0xFF);
|
|
}
|
|
cs_high(CS_PIN);
|
|
}
|
|
|
|
/* Helper function that increments PEC counters */
|
|
void ADBMS181x_check_pec(uint8_t total_ic, // Number of ICs in the system
|
|
uint8_t reg, // Type of Register
|
|
cell_asic *ic // A two dimensional array that stores the data
|
|
)
|
|
{
|
|
switch (reg)
|
|
{
|
|
case CFGR:
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
ic[current_ic].crc_count.pec_count = ic[current_ic].crc_count.pec_count + ic[current_ic].config.rx_pec_match;
|
|
ic[current_ic].crc_count.cfgr_pec = ic[current_ic].crc_count.cfgr_pec + ic[current_ic].config.rx_pec_match;
|
|
}
|
|
break;
|
|
|
|
case CFGRB:
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
ic[current_ic].crc_count.pec_count = ic[current_ic].crc_count.pec_count + ic[current_ic].configb.rx_pec_match;
|
|
ic[current_ic].crc_count.cfgr_pec = ic[current_ic].crc_count.cfgr_pec + ic[current_ic].configb.rx_pec_match;
|
|
}
|
|
break;
|
|
case CELL:
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
for (int i = 0; i < ic[0].ic_reg.num_cv_reg; i++)
|
|
{
|
|
ic[current_ic].crc_count.pec_count = ic[current_ic].crc_count.pec_count + ic[current_ic].cells.pec_match[i];
|
|
ic[current_ic].crc_count.cell_pec[i] = ic[current_ic].crc_count.cell_pec[i] + ic[current_ic].cells.pec_match[i];
|
|
}
|
|
}
|
|
break;
|
|
case AUX:
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
for (int i = 0; i < ic[0].ic_reg.num_gpio_reg; i++)
|
|
{
|
|
ic[current_ic].crc_count.pec_count = ic[current_ic].crc_count.pec_count + (ic[current_ic].aux.pec_match[i]);
|
|
ic[current_ic].crc_count.aux_pec[i] = ic[current_ic].crc_count.aux_pec[i] + (ic[current_ic].aux.pec_match[i]);
|
|
}
|
|
}
|
|
|
|
break;
|
|
case STAT:
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
|
|
for (int i = 0; i < ic[0].ic_reg.num_stat_reg - 1; i++)
|
|
{
|
|
ic[current_ic].crc_count.pec_count = ic[current_ic].crc_count.pec_count + ic[current_ic].stat.pec_match[i];
|
|
ic[current_ic].crc_count.stat_pec[i] = ic[current_ic].crc_count.stat_pec[i] + ic[current_ic].stat.pec_match[i];
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Helper Function to reset PEC counters */
|
|
void ADBMS181x_reset_crc_count(uint8_t total_ic, // Number of ICs in the system
|
|
cell_asic *ic // A two dimensional array that stores the data
|
|
)
|
|
{
|
|
for (int current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
ic[current_ic].crc_count.pec_count = 0;
|
|
ic[current_ic].crc_count.cfgr_pec = 0;
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
ic[current_ic].crc_count.cell_pec[i] = 0;
|
|
}
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
ic[current_ic].crc_count.aux_pec[i] = 0;
|
|
}
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
ic[current_ic].crc_count.stat_pec[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Helper function to initialize CFG variables */
|
|
void ADBMS181x_init_cfg(uint8_t total_ic, // Number of ICs in the system
|
|
cell_asic *ic // A two dimensional array that stores the data
|
|
)
|
|
{
|
|
for (uint8_t current_ic = 0; current_ic < total_ic; current_ic++)
|
|
{
|
|
for (int j = 0; j < 6; j++)
|
|
{
|
|
ic[current_ic].config.tx_data[j] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Helper function to set CFGR variable */
|
|
void ADBMS181x_set_cfgr(uint8_t nIC, // Current IC
|
|
cell_asic *ic, // A two dimensional array that stores the data
|
|
bool refon, // The REFON bit
|
|
bool adcopt, // The ADCOPT bit
|
|
bool gpio[5], // The GPIO bits
|
|
bool dcc[12], // The DCC bits
|
|
bool dcto[4], // The Dcto bits
|
|
uint16_t uv, // The UV value
|
|
uint16_t ov // The OV value
|
|
)
|
|
{
|
|
ADBMS181x_set_cfgr_refon(nIC, ic, refon);
|
|
ADBMS181x_set_cfgr_adcopt(nIC, ic, adcopt);
|
|
ADBMS181x_set_cfgr_gpio(nIC, ic, gpio);
|
|
ADBMS181x_set_cfgr_dis(nIC, ic, dcc);
|
|
ADBMS181x_set_cfgr_dcto(nIC, ic, dcto);
|
|
ADBMS181x_set_cfgr_uv(nIC, ic, uv);
|
|
ADBMS181x_set_cfgr_ov(nIC, ic, ov);
|
|
}
|
|
|
|
/* Helper function to set the REFON bit */
|
|
void ADBMS181x_set_cfgr_refon(uint8_t nIC, cell_asic *ic, bool refon)
|
|
{
|
|
if (refon)
|
|
ic[nIC].config.tx_data[0] = ic[nIC].config.tx_data[0] | 0x04;
|
|
else
|
|
ic[nIC].config.tx_data[0] = ic[nIC].config.tx_data[0] & 0xFB;
|
|
}
|
|
|
|
/* Helper function to set the ADCOPT bit */
|
|
void ADBMS181x_set_cfgr_adcopt(uint8_t nIC, cell_asic *ic, bool adcopt)
|
|
{
|
|
if (adcopt)
|
|
ic[nIC].config.tx_data[0] = ic[nIC].config.tx_data[0] | 0x01;
|
|
else
|
|
ic[nIC].config.tx_data[0] = ic[nIC].config.tx_data[0] & 0xFE;
|
|
}
|
|
|
|
/* Helper function to set GPIO bits */
|
|
void ADBMS181x_set_cfgr_gpio(uint8_t nIC, cell_asic *ic, bool gpio[5])
|
|
{
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
if (gpio[i])
|
|
ic[nIC].config.tx_data[0] = ic[nIC].config.tx_data[0] | (0x01 << (i + 3));
|
|
else
|
|
ic[nIC].config.tx_data[0] = ic[nIC].config.tx_data[0] & (~(0x01 << (i + 3)));
|
|
}
|
|
}
|
|
|
|
/* Helper function to control discharge */
|
|
void ADBMS181x_set_cfgr_dis(uint8_t nIC, cell_asic *ic, bool dcc[12])
|
|
{
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
if (dcc[i])
|
|
ic[nIC].config.tx_data[4] = ic[nIC].config.tx_data[4] | (0x01 << i);
|
|
else
|
|
ic[nIC].config.tx_data[4] = ic[nIC].config.tx_data[4] & (~(0x01 << i));
|
|
}
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (dcc[i + 8])
|
|
ic[nIC].config.tx_data[5] = ic[nIC].config.tx_data[5] | (0x01 << i);
|
|
else
|
|
ic[nIC].config.tx_data[5] = ic[nIC].config.tx_data[5] & (~(0x01 << i));
|
|
}
|
|
}
|
|
|
|
/* Helper function to control discharge time value */
|
|
void ADBMS181x_set_cfgr_dcto(uint8_t nIC, cell_asic *ic, bool dcto[4])
|
|
{
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
if (dcto[i])
|
|
ic[nIC].config.tx_data[5] = ic[nIC].config.tx_data[5] | (0x01 << (i + 4));
|
|
else
|
|
ic[nIC].config.tx_data[5] = ic[nIC].config.tx_data[5] & (~(0x01 << (i + 4)));
|
|
}
|
|
}
|
|
|
|
/* Helper Function to set UV value in CFG register */
|
|
void ADBMS181x_set_cfgr_uv(uint8_t nIC, cell_asic *ic, uint16_t uv)
|
|
{
|
|
uint16_t tmp = (uv / 16) - 1;
|
|
ic[nIC].config.tx_data[1] = 0x00FF & tmp;
|
|
ic[nIC].config.tx_data[2] = ic[nIC].config.tx_data[2] & 0xF0;
|
|
ic[nIC].config.tx_data[2] = ic[nIC].config.tx_data[2] | ((0x0F00 & tmp) >> 8);
|
|
}
|
|
|
|
/* Helper function to set OV value in CFG register */
|
|
void ADBMS181x_set_cfgr_ov(uint8_t nIC, cell_asic *ic, uint16_t ov)
|
|
{
|
|
uint16_t tmp = (ov / 16);
|
|
ic[nIC].config.tx_data[3] = 0x00FF & (tmp >> 4);
|
|
ic[nIC].config.tx_data[2] = ic[nIC].config.tx_data[2] & 0x0F;
|
|
ic[nIC].config.tx_data[2] = ic[nIC].config.tx_data[2] | ((0x000F & tmp) << 4);
|
|
}
|