4.30.1. ADI ADES1830
The communication with the AFE is made via a daisy-chain, which means that all the AFEs take part in every transmission.
There are 3 types of communications:
Sending a command without data (e.g., start a measurement).
Writing a register.
Reading a register.
Commands are sent to the whole daisy-chain, which means that for commands the daisy-chain acts as a transmission line.
When reading and writing, the daisy-chain acts as a shift register. The registers read or written are 6 bytes wide.
The data packets are made of bytes (8 bit words). The driver was developed on a TI TMS570. As the TI TMS570 SPI can transmit up to 16 bit per word, all HAL SPI function work with 16 bit words. As a consequence, all AFE functions work with 16 bit words, but each word must only contain one byte (i.e., the value must not be greater than 255).
A common parameter to all AFE functions has the type ADI_STATE_s
.
This structure is used to control the flow of the AFE driver.
The structure holds the following information
(for implementation details see
here):
A boolean indicating if the driver has been initialized or not, i.e., whether the first measurement has been started or not.
A boolean set to
true
after the first measurement was made.The number of SPI interfaces to be used. Usually it corresponds to the number of strings in the system.
The number of the string currently addressed.
A table used to store the redundant GPIO channel being used. This is an internal variable for the driver and should not be modified.
The serial IDs read from the AFEs in the daisy-chain.
The revision read from the AFEs in the daisy-chain.
A structure of the type
ADI_DATA_s
. It is used to store pointers to all relevant data (e.g., measured cell voltages, measured temperatures). It also stores the command counter for each string and an error table with all error flags.
The ADI_DATA_s
structure contains the following data
(for implementation details see
here):
A pointer to the SPI receive and the transmit buffer
All measurements (e.g., cell voltages, cell temperatures, GPIO voltages)
The command counter for each AFE
The error table, a structure of the type
ADI_ERROR_TABLE_s
An internal variable of the driver used for redundant auxiliary voltage check.
The ADI_ERROR_TABLE_s
structure contains the error status of the driver
(for implementation details see
here):
Flag indicating if PEC correct or not
Flag indicating if the command counter of the driver matches the internal command counter of the AFEs
Flag indicating if the configuration read from the AFEs matches the written configuration
4.30.1.1. Functions to adapt to change environment
The AFE driver in its current form is designed to work within foxBMS 2, using FreeRTOS. In order to use it in another environment (e.g., bare metal), it must be adapted in the following places.
4.30.1.1.1. Definitions
The function used in the AFE driver make use of the following enum as return value:
/** enum for standard return type */
typedef enum {
STD_OK, /**< ok */
STD_NOT_OK, /**< not ok */
} STD_RETURN_TYPE_e;
It must be added if it is not defined.
Dedicated structures are used to store the data retrieved by the driver. If they are not defined, the following definitions must be added:
/** data block struct of cell voltages */
typedef struct {
int16_t cellVoltage_mV[ADI_NR_OF_STRINGS][ADI_NR_OF_CELL_BLOCKS_PER_STRING]; /*!< unit: mV */
} DATA_BLOCK_CELL_VOLTAGE_s;
/** data block struct of GPIO voltages */
typedef struct {
int16_t gpioVoltages_mV[ADI_NR_OF_STRINGS]
[ADI_NR_OF_MODULES_PER_STRING * ADI_NR_OF_GPIOS_PER_MODULE]; /*!< unit: mV */
int16_t gpaVoltages_mV[ADI_NR_OF_STRINGS]
[ADI_NR_OF_MODULES_PER_STRING * ADI_NR_OF_GPAS_PER_MODULE]; /*!< unit: mV */
} DATA_BLOCK_ALL_GPIO_VOLTAGES_s;
/** data block struct of cell temperatures */
typedef struct {
int16_t cellTemperature_ddegC[ADI_NR_OF_STRINGS][ADI_NR_OF_TEMP_SENSORS_PER_STRING]; /*!< unit: deci °C */
} DATA_BLOCK_CELL_TEMPERATURE_s;
/** data block struct of balancing control */
typedef struct {
uint8_t balancingState[ADI_NR_OF_STRINGS]
[ADI_NR_OF_CELL_BLOCKS_PER_STRING]; /*!< 0: no balancing, 1: balancing active */
} DATA_BLOCK_BALANCING_CONTROL_s;
/** data block struct of cell open wire */
typedef struct {
uint8_t openWire[ADI_NR_OF_STRINGS]
[ADI_NR_OF_MODULES_PER_STRING *
(ADI_NR_OF_CELL_BLOCKS_PER_MODULE + 1u)]; /*!< 1 -> open wire, 0 -> everything ok */
} DATA_BLOCK_OPEN_WIRE_s;
They must be added at the place in code marked with:
/* If needed, add definition of database entries here */
in the file adi_ades183x_defs.h
.
In the file adi_cfg.h
, the following defines must be adapted:
#define ADI_NR_OF_STRINGS (BS_NR_OF_STRINGS)
#define ADI_NR_OF_MODULES_PER_STRING (BS_NR_OF_MODULES_PER_STRING)
#define ADI_NR_OF_CELL_BLOCKS_PER_MODULE (BS_NR_OF_CELL_BLOCKS_PER_MODULE)
#define ADI_NR_OF_GPIOS_PER_MODULE (BS_NR_OF_GPIOS_PER_MODULE)
#define ADI_NR_OF_TEMP_SENSORS_PER_MODULE (BS_NR_OF_TEMP_SENSORS_PER_MODULE)
#define ADI_MAX_SUPPORTED_CELLS (16u)
ADI_NR_OF_STRINGS
must be set to 1u
if only one string is used.
If several strings are used, the SPI function must be adapted to take this
into account.
ADI_NR_OF_MODULES_PER_STRING
is the number of modules in one daisy-chain
so corresponds to the number of AFEs in a daisy-chain.
ADI_NR_OF_CELL_BLOCKS_PER_MODULE
is the number of cells connected to
an AFE.
ADI_NR_OF_GPIOS_PER_MODULE
is the number of GPIOs available on an AFE.
It must be set to 10u
.
ADI_NR_OF_TEMP_SENSORS_PER_MODULE
is the number of temperature sensors
connected to the GPIOs.
It cannot be higher than ADI_NR_OF_GPIOS_PER_MODULE
.
ADI_MAX_SUPPORTED_CELLS
is the maximum number of cell voltage inputs
present on the AFE.
For the ADES1830, it must be set to 16
.
4.30.1.1.2. Static functions
ADI_SpiTransmitReceiveData()
is the function used to trigger
transmission over SPI.
It takes four parameters:
a struct of the type
ADI_STATE_s
, which is used to determine which string has to be addresseda pointer to the transmit buffer
a pointer to the receive buffer
the number of bytes to be transmitted
Three cases must be considered, each contained in the markers
/* START SPI function to adapt for different environment */
/* Code to be defined here to make SPI transmission */
/* END SPI function to adapt for different environment */
in the code. There are three cases to consider:
case 1: if a length of
0
is used, this means that the function must only send a dummy byte.if a length greater than
0
is used:case 2: if the pointer to the receive buffer has the value
NULL_PTR
, this means that the function has only data to transmit and that the data received on the SPI interface is discarded.case 3: otherwise the function transmits and receives data.
Warning
For the last two cases, before the data transmission and/or reception, a dummy byte must be sent to wake up the AFE communication interface.
The function ADI_AccessToDatabase()
is used to store and retrieve the
data from the foxBMS 2 database.
If the database is not used, the function content can simply be removed.
The function ADI_Wait()
receives an integer parameter.
The function must wait for the number of milliseconds passed as parameter.
In foxBMS 2, it blocks the FreeRTOS task running the AFE driver.
The AFE driver looks for requests to start or stop.
Requests are made made with the extern function ADI_MakeRequest()
.
The static function ADI_GetRequest()
is used to retrieve the requests
made to the driver.
In foxBMS 2, a FreeRTOS queue is used.
If this environment is not used, the request mechanism must be adapted
accordingly and the reference to the queue must be removed if the queue is not
used.
If the request mechanism is not needed or for debugging purposes, the
function ADI_GetRequest()
can simply be changed to always set the
request to ADI_START_REQUEST
.
The functions OS_EnterTaskCritical()
and OS_ExitTaskCritical()
are
used to prevent interrupts during the setting of the flags to ensure the
validity of the flags.
They must be replaced by an appropriate protection mechanism if FreeRTOS
is not used.
4.30.1.1.3. Extern functions
In foxBMS 2, a FreeRTOS queue is used in the function ADI_MakeRequest()
.
If this environment is not used, the request mechanism must be adapted
accordingly and the reference to the queue must be removed if the queue is
not used.
As explained above, if requests are not needed or for debugging
purposes, if the function ADI_GetRequest()
is changed to always set the
request to ADI_START_REQUEST
, the function ADI_MakeRequest()
can
simply be changed to do nothing.
The function ADI_ActivateInterfaceBoard()
is used to drive an I2C port
expander, to toggle activation pins in order to activate the interface board
communicating with the slave.
It must be adapted to the hardware used.
The function ADI_ConvertGpioVoltageToTemperature()
converts a GPIO
voltage in mV to a temperature in deci Celsius.
It must be adapted to the hardware used.
4.30.1.2. Primitives for communication
As stated before, the communication with the daisy-chain is made via three basic operations. They are detailed in this section.
Each transaction with the daisy-chain starts with a command. It is then followed by data when reading from or writing to the AFE, or by nothing for command not involving data (e.g., when triggering a measurement).
Each command is accessible via a dedicated variable, like
const uint16_t adi_cmdWrcfga[4u] = {ADI_WRCFGA_BYTE0, ADI_WRCFGA_BYTE1, ADI_WRCFGA_INC, ADI_WRCFGA_LEN};
to write configuration register A. Each variable has four fields:
command byte 0
command byte 1
1 if the command increments the command counter, 0 otherwise
data length in bytes. For writing and reading register, it is 6. For commands not involving data, it can be set to 0.
A list of defines was created for these fields for each command.
They are located in the file adi_ades183x_defs.h
.
4.30.1.2.1. Sending commands without data
The function ADI_TransmitCommand()
is used for this purpose.
It has four parameters:
a
uint16_t
pointer. As commands sent to the AFE are made out of 2 bytes, it must point to two bytes, corresponding to the used command as defined in the data sheet. One of the commands defined as explained above must be used.a pointer to the
ADI_STATE_s
structure.
The functions to send commands are illustrated in Fig. 4.11.
Some commands have configurable bits (e.g., measurement commands, in order
to change measurement parameters).
Base commands have been defined as constant variables and for concerned
commands, the configurable bits are set to 0.
The function ADI_TransmitCommand()
takes the command
as a non-constant variable, so the following procedure must be used:
Use
ADI_CopyCommandBits()
to copy the constant command definition to a non constant variable. This variable must be anuint16_t
table of length 4.If necessary, use
ADI_WriteCommandConfigurationBits()
to modify the configurable bits as needed.Call
ADI_TransmitCommand()
with the non constant variable.
The function realizes the following operations:
The function computes the PEC of the two command bytes and then sends 4 bytes (2 bytes command followed by two bytes PEC).
If the command sent increments the AFE command counter, the command counter stored in the driver and accessed via the
ADI_DATA_s
structure in theADI_STATE_s
structure is increased. The comparison between the AFE command counter and the command counter stored by the driver is made in the functionADI_ReadRegister()
, as the AFE transmits its command counter in each answer frame.
4.30.1.2.2. Reading a register
The function ADI_ReadRegister()
is used for this purpose.
It has three parameters:
An
uint16_t
pointer to the command corresponding to the register to read. One of the commands defined as explained above must be used.A uint8_t pointer to the table where the read data is stored
A pointer to the
ADI_STATE_s
structure
The functions and variables to read registers are illustrated in Fig. 4.12.
The function realizes the following operations:
The SPI transmit/receive function is used to transmit the command and receive the data:
First the two bytes of the command must be transmitted, followed by the two command PEC bytes. The command PEC is computed with the PEC15 function.
Then the AFE ICs in the daisy-chain transform into a shift-register to transmit the data to be read while the MCU receives it. This data consists of one frame for each AFE. Each frame contains 4 or 6 bytes, depending on the register size, followed by two bytes of data PEC. Data PEC is computed with the PEC10 function.
The function computes the PEC of each data frame and compare it to the PEC sent by each AFE. If it does not match, the corresponding
crcIsOk
variable in theADI_ERROR_TABLE_s
structure is set tofalse
. It is set totrue
otherwise.The function extract the command counter sent by each AFE and compares it with the value stored in the driver in the
ADI_DATA_s
structure. If it does not match, the correspondingcommandCounterIsOk
variable in theADI_ERROR_TABLE_s
structure is set tofalse
. It is set totrue
otherwise.
The variable
uint8_t adi_dataReceive[ADI_NR_OF_MODULES_PER_STRING * ADI_MAX_REGISTER_SIZE_IN_BYTES]
is a general purpose variable used throughout the driver as a buffer when
using the read function.
4.30.1.2.3. Writing a register
The function ADI_WriteRegister()
is used for this purpose.
It has three parameters:
A
uint16_t
pointer to the command corresponding to the register to write. One of the commands defined as explained above must be used.A uint8_t pointer to the table where the data to be written is stored
A variable of the type
ADI_PEC_FAULT_INJECTION_e
used for fault injection.A pointer to the
ADI_STATE_s
structure
The functions and variables to write registers are illustrated in Fig. 4.13.
The function realizes the following operations:
The data to be sent for the transmission is prepared:
First the two bytes of the command must be transmitted, followed by the two command PEC bytes. Command PEC is computed with the PEC15 function.
Then the PEC of the written data must be computed. This data consists of one frame for each AFE. Each frame contains 4 or 6 bytes, depending on the register size, followed by two bytes of data PEC. Data PEC is computed with the PEC10 function.
The SPI transmit/receive function is used to transmit the command and followed by the data. After the command was received, the AFEs in the daisy-chain transform into a shift-register to receive the data to be written while the MCU transmits it.
The fault injection can be of 3 types:
ADI_PEC_NO_FAULT_INJECTION
, the write process is made without fault injectionADI_COMMAND_PEC_FAULT_INJECTION
, the write process is made and the command PEC is modified so that it does not correspond anymore to the command sent for writingADI_DATA_PEC_FAULT_INJECTION
, the write process is made and the data is modified so that it does not correspond anymore to the data PEC sent
The variable
uint8_t adi_dataTransmit[ADI_NR_OF_MODULES_PER_STRING * ADI_MAX_REGISTER_SIZE_IN_BYTES]
is a general purpose variable used throughout the driver as a buffer when
using the write function.
The function ADI_WriteRegisterGlobal()
is equivalent to
ADI_WriteRegister()
, writing the same 6 bytes of data to
all AFEs in the daisy-chain.
It is a practical way to write the same data to all AFEs.
The variable uint8_t adi_writeGlobal[ADI_MAX_REGISTER_SIZE_IN_BYTES]
is a
buffer used to write the same 6 bytes (ADI_MAX_REGISTER_SIZE_IN_BYTES
equals 6) of data to a register for all the AFE ICs in the daisy-chain.
4.30.1.3. Important variables used by the driver
uint16_t adi_bufferRxPec[ADI_N_BYTES_FOR_DATA_TRANSMISSION]
and
uint16_t adi_bufferTxPec[ADI_N_BYTES_FOR_DATA_TRANSMISSION]
are the SPI buffers used during all transmissions.
The number of bytes is
(ADI_COMMAND_AND_PEC_SIZE_IN_BYTES + ((ADI_MAX_REGISTER_SIZE_IN_BYTES + ADI_PEC_SIZE_IN_BYTES) * ADI_N_ADI))
or 4+(6+2)*ADI_N_ADI
.
The transmission uses 2 bytes for the command, followed by 2 command PEC bytes,
followed by the data.
For each AFE in the daisy-chain, the transmission has maximum 6 bytes of data,
each followed by 2 data PEC bytes, hence the number of
data bytes being 6+2 time the number of AFEs in the daisy-chain.
In foxBMS 2, as the AFE driver uses SPI with DMA, these two variables MUST
reside in a non cache-able area.
These two variables are internal to the working of the AFE driver and should
not be used for other purposes.
4.30.1.4. Helper functions to access bit fields in registers
The function ADI_ReadDataBits()
is used to extract a bit field from data
that has been read.
Its parameters are:
Read byte from daisy-chain, from which data field is to be extracted.
Pointer to memory location (1 byte) where extracted data field will be written.
Position of bit field to extract.
Mask of bit field to extract.
The function ADI_WriteDataBits()
is used to write a bit field in data
that has to be written.
Its parameters are:
Pointer to byte that will be written to daisy-chain, where data from bit field will be written.
Byte containing data to be written to bit field.
Position of bit field to extract.
Mask of bit field to extract.
Bit field position and bit field masks are defined for all entries of all
configuration registers in adi_ades183x_defs.h
.
When writing a bit field in a byte with ADI_WriteDataBits()
, the other
bit fields in the byte remain unchanged.
4.30.1.5. Configuration of the AFE
The driver should ensure that the configuration stored on the AFE ICs
in the daisy-chain corresponds to the configuration that was written.
As a consequence, the driver holds a copy of the configuration that is written
to the AFE ICs in the daisy-chain.
There are 2 tables, one for configuration register A and one for configuration
register B.
They are named adi_configurationRegisterAgroup[]
and
adi_configurationRegisterBgroup[]
.
Both tables have the size
[ADI_NR_OF_STRINGS][ADI_NR_OF_MODULES_PER_STRING * ADI_MAX_REGISTER_SIZE_IN_BYTES]
where ADI_MAX_REGISTER_SIZE_IN_BYTES
which equals 6u is the
register size in bytes: the content of each configuration register
must be stored for each AFE in the daisy-chain.
In addition, this must be done for each string.
In Fig. 4.14, the tables are represented to ease the comprehension.
When writing the configuration, the driver also reads it to check that it was
written correctly.
For this purpose, another set of tables exists, named
adi_readConfigurationRegisterAgroup[]
and
adi_readConfigurationRegisterBgroup[]
.
They have the same size as the variables used to write the configuration.
It must be noted that the figure only shows the tables for one string. There is one configuration table set for each string.
The procedure to set a specific configuration is made out of two steps:
Change the configuration in the configuration tables
adi_configurationRegisterAgroup[]
and/oradi_configurationRegisterBgroup[]
.Write the configuration of the tables to the AFEs in the daisy-chain.
4.30.1.5.1. Changing configuration in tables
The functions to change configuration and their interaction with the variables are illustrated in Fig. 4.15.
Two functions are available change the configuration in the configuration
tables: ADI_StoredConfigurationFillRegisterData()
and
ADI_StoredConfigurationFillRegisterDataGlobal()
.
The function ADI_StoredConfigurationFillRegisterData()
is used to
modify the values in a specific byte of a register, for one specific
IC in the daisy-chain.
Its parameters are:
Module number corresponding to the AFE whose configuration must be changed.
ADI_CFG_REGISTER_SET_e
parameter. Used to specify which configuration register must be written (i.e., used to chose between register A and B).Register offset: corresponds to the byte position in the register. Defines are available in
adi_ades183x_defs.h
(ADI_REGISTER_OFFSET0
toADI_REGISTER_OFFSET0
).Data to be written to the bit field in the register.
Bit field position.
Bit field mask.
ADI_STATE_s
structure. The string number is passed via this structure.
ADI_StoredConfigurationFillRegisterData()
uses ADI_WriteDataBits()
,
so when writing a bit field in a byte, the other bit fields in the byte remain
unchanged.
The function ADI_StoredConfigurationFillRegisterDataGlobal()
simply
calls ADI_StoredConfigurationFillRegisterData()
for all modules in a
daisy-chain.
It is a useful and simpler way to set the same configuration
for all AFEs in the daisy-chain.
In Fig. 4.16, the use of the helper functions to modify the configuration tables is represented to ease the comprehension.
4.30.1.5.2. Writing the changes to the daisy-chain
To write the changes to the daisy-chain, the function
ADI_StoredConfigurationWriteToAfe()
must be called.
The first parameter is an enum of the type ADI_CFG_REGISTER_SET_e
.
It serves to chose which register from the configuration tables to write and
can have values ranging, ADI_CFG_REGISTER_SET_A
or
ADI_CFG_REGISTER_SET_B
.
When called, the function ADI_StoredConfigurationWriteToAfe()
writes
the data contained in the configurationRegisterX[]
variable to the
daisy-chain.
For instance, if the function is called with ADI_CFG_REGISTER_SET_B
as
first parameter, the content of adi_configurationRegisterBgroup[]
will be
written to the daisy-chain.
It must be noted that even if the tables have been changed, the change will
not be written to the daisy-chain until the function
ADI_StoredConfigurationWriteToAfe()
is called for the corresponding
register.
After the write operation is complete,
ADI_StoredConfigurationWriteToAfe()
reads the register it has just
written to and stores the results in the table
readConfigurationRegisterX[]
, where X
again corresponds to the
configuration register being accessed.
ADI_StoredConfigurationWriteToAfe()
then calls
ADI_CheckConfigurationRegister()
.
This function compares configurationRegisterX[]
and
readConfigurationRegisterX[]
.
If both table do not match, the corresponding (i.e., string and module number)
configurationIsOk
variable in the ADI_ERROR_TABLE_s
structure is set
to false
.
It is set to true
otherwise.
The function ADI_StoredConfigurationWriteToAfeGlobal()
is used to write
all configuration register at once.
It simply calls ADI_StoredConfigurationWriteToAfe()
for all configuration
registers.
ADI_StoredConfigurationWriteToAfe()
and
ADI_StoredConfigurationWriteToAfeGlobal()
use the primitives
ADI_WriteRegister()
and ADI_ReadRegister()
to communicate with
the daisy-chain.
4.30.1.5.3. Example of configuration write
The procedure to change the IIR filter setting for cell voltage measurement for all AFEs in the daisy-chain is as follows. The corresponding bit field is located in configuration register A, byte CFGAR5, bits [2:0].
First, set the configuration in adi_configurationRegisterAgroup[]
with
ADI_StoredConfigurationFillRegisterDataGlobal()
.
ADI_StoredConfigurationFillRegisterDataGlobal(
ADI_CFG_REGISTER_SET_A,
ADI_REGISTER_OFFSET0,
0b001u,
ADI_CFGRA5_FC_0_2_POS,
ADI_CFGRA5_FC_0_2_MASK,
adi_state);
Then call ADI_StoredConfigurationWriteToAfe()
ADI_StoredConfigurationWriteToAfe(ADI_CFG_REGISTER_SET_A, adi_state);
All bytes of the configuration register must be written during the transaction
but the function ADI_StoredConfigurationFillRegisterDataGlobal()
only
changes the filter bit field in byte CFGAR5 of configuration register A, so
the other bit fields in this byte and the other bytes are written but with
their current value, which means they remain unchanged.
4.30.1.6. Calling of the driver
The principal function of the AFE driver is ADI_MeasurementCycle()
.
It must be called once (i.e., it must not be called periodically), because it
already implements the measurement loop.
A structure of the type ADI_STATE_s
must be defined and passed as
a pointer.
It controls the flow of the driver.
The driver loop is structured as follows:
The driver starts making no measurements and check for a start request. As long as no request was received, the driver does nothing except checking for a request.
When a start request is received, the driver calls the initialization function
ADI_InitializeMeasurement()
.Then the driver performs a measurement cycle: it loops through all the strings and measures all relevant values and manages balancing.
After the measurement cycle, the driver check if a stop request comes. If yes, it goes back to the non-measuring state, waiting for a start request. If no, it performs another measurement cycle and the process goes on.
The variable firstMeasurementMade
of the ADI_STATE_s
structure
must be initialized with false
.
Once one measurement cycle was made, it is set to true
.
The variable measurementStarted
of the ADI_STATE_s
structure must be
initialized with false
and is set to true
of false
depending on
the state of the driver.
4.30.1.6.1. Initialization function
The function resets the error table with ADI_ResetErrorTable()
.
Then for each string:
Send a dummy byte with
ADI_WakeUp()
to wake up the daisy-chainClear the command counter with
ADI_ClearCommandCounter()
Set the default configuration of the AFE in two steps. First, write the tables
adi_configurationRegisterAgroup[]
andadi_configurationRegisterBgroup[]
, which is done inADI_InitializeConfiguration()
. Then callADI_StoredConfigurationWriteToAfeGlobal()
to write the tables to the daisy-chain.Check reset values of supply and reference measurements.
Clear values with the
CLRAUX
command.Check cleared values of supply and reference measurements.
Set all PWM balancing values to 0.
Clear all flags in Status Register Group C with the
CLRFLAG
command.Issue an
ADCV
command to set continuous measurements of C-ADCs and S-ADCS (S-ADCs are redundant measurements).Read revision of all AFEs in the daisy-chain.
4.30.1.6.2. Measurement sequence
The measurement sequence is as follows:
Issue an
ADAX
command to measure all GPIOs and all other voltages like supply and references.Issue an
ADAX2
command to measure one GPIO redundantly.Wait 10 ms.
Issue a
SNAP
command to freeze values in voltage registers .Get the cell voltages with
ADI_GetVoltages()
. This function reads the registers and stores the data in the chosen structures pointed by theADI_DATA_s
structure.Wait 8 ms. 18 ms is the time needed for the
ADAX
command to complete.Read the GPIO voltages with
ADI_GetGpioVoltages()
. This function reads the auxiliary registers and stores the data in the chosen structures pointed by theADI_DATA_s
structure.Convert the GPIO voltages to temperatures with
ADI_GetTemperatures()
. This function stores the temperatures in the structure pointed by theADI_DATA_s
structure.If the first measurement flag was not set, set it.
Read the balancing orders and activate balancing accordingly with
ADI_BalanceControl()
.Realize diagnostic functions with
ADI_Diagnostic()
(This is a dummy function).
The functions that retrieve data from the daisy-chain call the function
ADI_ReadRegister()
.
The balancing control is done by writing to the configuration registers.
The driver reads in the table adi_balancingControl
which cells have to
be balanced.
4.30.1.6.3. Reading measured cell voltages
For cell voltages, the function ADI_GetVoltages()
must be used.
Its first parameter is the ADI_DATA_s
structure containing the state
of the driver.
Two parameters are also passed:
ADI_VOLTAGE_REGISTER_TYPE_e registerType
: it is used to determine which register set must be read. Possible values:ADI_CELL_VOLTAGE_REGISTER
to read cell voltage register (RDCVx)ADI_AVERAGE_CELL_VOLTAGE_REGISTER
to read average cell voltage registers (RDACx)ADI_FILTERED_CELL_VOLTAGE_REGISTER
to read filtered cell voltage registers (RDFCx)ADI_REDUNDANT_CELL_VOLTAGE_REGISTER
to read S-voltage registers (RDSVx)
ADI_VOLTAGE_STORE_LOCATION_e storeLocation
: it is used to determine in which variable the read values will be stored. Possible values:ADI_CELL_VOLTAGE
: store indata.cellVoltage
ADI_AVERAGE_CELL_VOLTAGE
: store indata.cellVoltageAverage
ADI_FILTERED_CELL_VOLTAGE
: store indata.cellVoltageFiltered
ADI_REDUNDANT_CELL_VOLTAGE
: store indata.cellVoltageRedundant
ADI_CELL_VOLTAGE_OPEN_WIRE_EVEN
: store indata.cellVoltageOpenWireEven
ADI_CELL_VOLTAGE_OPEN_WIRE_ODD
: store indata.cellVoltageOpenWireOdd
Care must be taken when choosing the parameter values because registerType
and storeLocation
are independent.
Calling for instance
ADI_GetVoltages(adi_state, ADI_FILTERED_CELL_VOLTAGE_REGISTER, ADI_AVERAGE_CELL_VOLTAGE);
will cause the filtered voltage measurements to be stored in
data.cellVoltageAverage
, which is probably not what is intended.
4.30.1.6.4. Reading measured GPIO voltages
For GPIO voltages, the function ADI_GetGpioVoltages()
must be used.
Its first parameter is the ADI_DATA_s
structure containing the state
of the driver.
Two parameters are also passed:
ADI_AUXILIARY_REGISTER_TYPE_e registerType
: it is used to determine which register set must be read. Possible values:ADI_AUXILIARY_REGISTER
to read GPIO voltage register (RDAUXx)ADI_REDUNDANT_AUXILIARY_REGISTER
to read average cell voltage registers (RDRAXx)
ADI_AUXILIARY_STORE_LOCATION_e storeLocation
: it is used to determine in which variable the read values will be stored. Possible values:ADI_AUXILIARY_VOLTAGE
:data.allGpioVoltages
ADI_REDUNDANT_AUXILIARY_VOLTAGE
:data.allGpioVoltagesRedundant
ADI_AUXILIARY_VOLTAGE_OPEN_WIRE
:data.allGpioVoltageOpenWire
Care must be taken when choosing the parameter values because registerType
and storeLocation
are independent.
Calling for instance
adi_state, ADI_REDUNDANT_AUXILIARY_REGISTER, ADI_AUXILIARY_VOLTAGE);
will cause the redundant voltage measurements to be stored in
data.allGpioVoltages
, which is probably not what is intended.
4.30.1.7. CRC computations
For commands, PEC15 is used.
It is a 15 bit CRC with polynomial 0xC599
and seed 0x10
.
For data, PEC10 is used.
It is a 10 bit CRC with polynomial 0x48F
and seed 0x10
.
It is computed on the 6 bytes of data plus the 6 bits of the command counter.
For data to be written to the daisy-chain, the command counter bits are set to
0.
The scripts and documentation for the precomputed CRC tables are found at
tools/crc/crc-15_0xc599.md
and tools/crc/crc-10_0x48f.md
.
A C-implementation for the CRC pre-computation could look like this:
1int main() {
2 uint16_t adi_crc15Table[256];
3 for (uint16_t i = 0u; i <= 255u; i++) {
4 uint16_t data = i << (15u - 8u);
5 for (uint8_t bit = 0u; bit < 8u; bit++) {
6 if ((data & (1u << (15u - 1u))) != 0u) {
7 data <<= 1;
8 data ^= 0xC599u;
9 } else {
10 data <<= 1;
11 }
12 }
13 adi_crc15Table[i] = data & 0x7FFFu;
14 printf("0x%04Xu, ", adi_crc15Table[i]);
15 }
16 printf("\n");
17 return 0;
18}
1int main() {
2 uint16_t adi_crc10Table[256];
3 for (uint16_t i = 0u; i <= 255u; i++) {
4 uint16_t data = i << (10u - 8u);
5 for (uint8_t bit = 0u; bit < 8u; bit++) {
6 if ((data & (1u << (10u - 1u))) != 0u) {
7 data <<= 1;
8 data ^= 0x48Fu;
9 } else {
10 data <<= 1;
11 }
12 }
13 adi_crc10Table[i] = data & 0x3FFu;
14 printf("0x%04Xu, ", adi_crc10Table[i]);
15 }
16 printf("\n");
17 return 0;
18}
4.30.1.8. Parameters used to configure the driver
4.30.1.8.1. File adi_ades183x_cfg.h
ADI_DISCARD_PEC
is used as a debugging option.
If set to true
, the PEC checks are ignored.
In normal usage it must be set to false
.
4.30.1.8.2. File adi_ades1830_cfg.c
adi_voltageInputsUsed[]
is used to skip cell voltages.
It contains 16 times the value 1
.
All voltages are measured by the AFE but any value replaced by 0
will
result in the corresponding measured cell voltage input to be skipped when
storing the cell voltages.
Warning
The number of 1
must correspond to
BS_NR_OF_CELL_BLOCKS_PER_MODULE
.
adi_temperatureInputsUsed[]
is used to skip GPIO voltages.
It contains 10 times the value 1
.
All GPIOs are measured by the AFE but any value replaced by 0
will result
in the corresponding GPIO voltage input to be skipped when storing the
temperatures.
Warning
The number of 1
must correspond to
ADI_NR_OF_TEMP_SENSORS_PER_MODULE
.
4.30.1.9. Examples for standard operations
4.30.1.9.1. Reading a register
Steps:
Define a variable to hold the command information.
Copy the command information to it.
Optionally, set the desired values for configuration bits in the command.
Call
ADI_ReadRegister()
.The results are stored in the table passed as parameters. The table
adi_dataReceive[ADI_NR_OF_MODULES_PER_STRING * ADI_MAX_REGISTER_SIZE_IN_BYTES]
can be used.Typically, loop through all modules to extract the needed bytes.
ADI_ReadDataBits()
can be used to extract a specific field. Defines are available in the fileadi_defs.h
.
ADI_MAX_REGISTER_SIZE_IN_BYTES
has the value 6.
Code example to read Status Register Group C:
uint16_t adi_command[ADI_COMMAND_DEFINITION_LENGTH] = {0};
ADI_CopyCommandBits(adi_cmdRdstatc, adi_command);
ADI_ReadRegister(adi_command, adi_dataReceive, adi_state);
for (uint16_t m = 0u; m < ADI_N_ADI; m++) {
/* Get STR5 */
uint8_t statusData = adi_dataReceive[(m * ADI_RDSTATC_LEN) + ADI_REGISTER_OFFSET5];
/* Check COMP flag */
uint8_t flagComp = 0u;
ADI_ReadDataBits(statusData, &flagComp, ADI_STCR5_COMP_POS, ADI_STCR5_COMP_MASK);
if (flagComp != 1u) {
adi_state->data.errorTable->compFlagIsCleared[adi_state->currentString][m] = false;
}
}
4.30.1.9.2. Writing a register
The function ADI_WriteRegister()
is used to write a register.
The command information is passed directly to the function.
The table adi_dataTransmit[ADI_NR_OF_MODULES_PER_STRING * ADI_MAX_REGISTER_SIZE_IN_BYTES]
can be used to store the data to write.
The parameter pecFaultInjection
should always have the value
ADI_PEC_NO_FAULT_INJECTION
, otherwise faults will be injected and
the write operation will not be valid.
Code example to write PMW Register Group A:
ADI_WriteRegister(adi_cmdWrpwma, adi_dataTransmit, ADI_PEC_NO_FAULT_INJECTION, adi_state);
If the same data must be written to all AFEs in the daisy chain, the
function ADI_WriteRegisterGlobal()
can be used.
The parameters are similar except for the data table: it is only 6 bytes wide.
The table
adi_dataTransmit[ADI_NR_OF_MODULES_PER_STRING * ADI_MAX_REGISTER_SIZE_IN_BYTES]
can be used to store the data to write.
Code example to write PMW Register Group A, same 6 bytes for all AFEs in the daisy-chain:
ADI_WriteRegisterGlobal(adi_cmdWrpwma, adi_writeGlobal, ADI_PEC_NO_FAULT_INJECTION, adi_state);
The variable adi_writeGlobal[ADI_MAX_REGISTER_SIZE_IN_BYTES]
can be
used to hold the 6 bytes that must be written in the desired register for
AFEs in the daisy-chain.
4.30.1.9.3. Sending a command
Steps:
Define a variable to hold the command information.
Copy the command information to it.
Optionally, set the desired values for configuration bits in the command. The function
ADI_WriteCommandConfigurationBits()
is available for this purpose. Defines are available in the filesadi_ades183x_defs.h
andadi_ades1830_defs.h
for the setup bits.
The last parameter must be NULL_PTR
when no data is sent with the
command.
Code example to send a command without data:
uint16_t adi_command[ADI_COMMAND_DEFINITION_LENGTH] = {0};
ADI_CopyCommandBits(adi_cmdAdcv, adi_command);
ADI_WriteCommandConfigurationBits(adi_command, ADI_ADCV_RD_POS, ADI_ADCV_RD_LEN, 1u);
ADI_WriteCommandConfigurationBits(adi_command, ADI_ADCV_CONT_POS, ADI_ADCV_CONT_LEN, 1u);
ADI_WriteCommandConfigurationBits(adi_command, ADI_ADCV_DCP_POS, ADI_ADCV_DCP_LEN, 0u);
ADI_WriteCommandConfigurationBits(adi_command, ADI_ADCV_RSTF_POS, ADI_ADCV_RSTF_LEN, 1u);
ADI_WriteCommandConfigurationBits(adi_command, ADI_ADCV_OW01_POS, ADI_ADCV_OW01_LEN, 0u);
ADI_TransmitCommand(adi_command, adi_state, NULL_PTR);
The CLRFLAG command must be sent with data to indicate which flags must be
cleared.
The variable adi_clearFlagData[ADI_CLRFLAG_DATA_LENGTH]
can be
used for this purpose.
ADI_CLRFLAG_DATA_LENGTH
has the value 6.
adi_clearFlagData[]
must be set without the desired value and passed
instead of NULL_PTR
.
Code example to send a command with data:
ADI_CopyCommandBits(adi_cmdClrflag, adi_command);
adi_clearFlagData[ADI_REGISTER_OFFSET0] = 0u;
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET0], 1u, ADI_STCR0_CS1FLT_POS, ADI_STCR0_CS1FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET0], 1u, ADI_STCR0_CS2FLT_POS, ADI_STCR0_CS2FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET0], 1u, ADI_STCR0_CS3FLT_POS, ADI_STCR0_CS3FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET0], 1u, ADI_STCR0_CS4FLT_POS, ADI_STCR0_CS4FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET0], 1u, ADI_STCR0_CS5FLT_POS, ADI_STCR0_CS5FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET0], 1u, ADI_STCR0_CS6FLT_POS, ADI_STCR0_CS6FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET0], 1u, ADI_STCR0_CS7FLT_POS, ADI_STCR0_CS7FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET0], 1u, ADI_STCR0_CS8FLT_POS, ADI_STCR0_CS8FLT_MASK);
adi_clearFlagData[ADI_REGISTER_OFFSET1] = 0u;
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET1], 1u, ADI_STCR1_CS9FLT_POS, ADI_STCR1_CS9FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET1], 1u, ADI_STCR1_CS10FLT_POS, ADI_STCR1_CS10FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET1], 1u, ADI_STCR1_CS11FLT_POS, ADI_STCR1_CS11FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET1], 1u, ADI_STCR1_CS12FLT_POS, ADI_STCR1_CS12FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET1], 1u, ADI_STCR1_CS13FLT_POS, ADI_STCR1_CS13FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET1], 1u, ADI_STCR1_CS14FLT_POS, ADI_STCR1_CS14FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET1], 1u, ADI_STCR1_CS15FLT_POS, ADI_STCR1_CS15FLT_MASK);
ADI_WriteDataBits(&adi_clearFlagData[ADI_REGISTER_OFFSET1], 1u, ADI_STCR1_CS16FLT_POS, ADI_STCR1_CS16FLT_MASK);
adi_clearFlagData[ADI_REGISTER_OFFSET2] = 0u;
adi_clearFlagData[ADI_REGISTER_OFFSET3] = 0u;
adi_clearFlagData[ADI_REGISTER_OFFSET4] = 0u;
adi_clearFlagData[ADI_REGISTER_OFFSET5] = 0u;
ADI_TransmitCommand(adi_command, adi_state, adi_clearFlagData);