.. include:: ./../../../../../macros.txt .. include:: ./../../../../../units.txt .. _ADI_ADES1830: 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: - 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: - 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: - 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 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. Definitions ^^^^^^^^^^^ The function used in the AFE driver make use of the following enum as return value: .. code-block:: c /** 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: .. code-block:: c /** 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 { bool activateBalancing[ADI_NR_OF_STRINGS][ADI_NR_OF_MODULES_PER_STRING] [ADI_NR_OF_CELL_BLOCKS_PER_MODULE]; /*!< 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: .. code-block:: c /* 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: .. code-block:: c #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``. 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 addressed - a 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 .. code-block:: c /* 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. 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. 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 .. code-block:: c 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``. 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 :numref:`send-command-ades1830`. .. figure:: img/ades1830/adi_ades1830_primitive_send_command.png :alt: Functions used to send commands :name: send-command-ades1830 :align: center :width: 35 % Functions used to send commands 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 an ``uint16_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 the ``ADI_STATE_s`` structure is increased. The comparison between the AFE command counter and the command counter stored by the driver is made in the function ``ADI_ReadRegister()``, as the AFE transmits its command counter in each answer frame. 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 :numref:`read-register-ades1830`. .. figure:: img/ades1830/adi_ades1830_primitive_read_register.png :alt: Functions and variables used to read registers :name: read-register-ades1830 :align: center :width: 55 % Functions and variables used to read registers 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 the ``ADI_ERROR_TABLE_s`` structure is set to ``false``. It is set to ``true`` 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 corresponding ``commandCounterIsOk`` variable in the ``ADI_ERROR_TABLE_s`` structure is set to ``false``. It is set to ``true`` 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. 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 :numref:`write-register-ades1830`. .. figure:: img/ades1830/adi_ades1830_primitive_write_register.png :alt: Functions and variables used to write registers :name: write-register-ades1830 :align: center :width: 55 % Functions and variables used to write registers 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 injection - ``ADI_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 writing - ``ADI_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. 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|, 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. 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. 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 :numref:`configuration-tables-ades1830`, the tables are represented to ease the comprehension. .. figure:: img/ades1830/adi_ades1830_configuration_tables.png :alt: Tables holding configuration :name: configuration-tables-ades1830 :align: center :width: 55 % Tables holding configuration 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/or ``adi_configurationRegisterBgroup[]``. - Write the configuration of the tables to the AFEs in the daisy-chain. Changing configuration in tables ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The functions to change configuration and their interaction with the variables are illustrated in :numref:`configuration-procedure-ades1830`. .. figure:: img/ades1830/adi_ades1830_configuration_procedure.png :alt: Functions and variables used to write registers :name: configuration-procedure-ades1830 :align: center :width: 100 % Functions and variables used to change configuration 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`` to ``ADI_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 :numref:`configuration-tables-ades1830-modification`, the use of the helper functions to modify the configuration tables is represented to ease the comprehension. .. figure:: img/ades1830/adi_ades1830_configuration_tables_modification.png :alt: Modification of tables holding configuration :name: configuration-tables-ades1830-modification :align: center :width: 55 % Modification of tables holding configuration 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. 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()``. .. code-block:: c 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()`` .. code-block:: c 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. 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. 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-chain - Clear the command counter with ``ADI_ClearCommandCounter()`` - Set the default configuration of the AFE in two steps. First, write the tables ``adi_configurationRegisterAgroup[]`` and ``adi_configurationRegisterBgroup[]``, which is done in ``ADI_InitializeConfiguration()``. Then call ``ADI_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. 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 the ``ADI_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 the ``ADI_DATA_s`` structure. - Convert the GPIO voltages to temperatures with ``ADI_GetTemperatures()``. This function stores the temperatures in the structure pointed by the ``ADI_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. 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 in ``data.cellVoltage`` - ``ADI_AVERAGE_CELL_VOLTAGE``: store in ``data.cellVoltageAverage`` - ``ADI_FILTERED_CELL_VOLTAGE``: store in ``data.cellVoltageFiltered`` - ``ADI_REDUNDANT_CELL_VOLTAGE``: store in ``data.cellVoltageRedundant`` - ``ADI_CELL_VOLTAGE_OPEN_WIRE_EVEN``: store in ``data.cellVoltageOpenWireEven`` - ``ADI_CELL_VOLTAGE_OPEN_WIRE_ODD``: store in ``data.cellVoltageOpenWireOdd`` Care must be taken when choosing the parameter values because ``registerType`` and ``storeLocation`` are independent. Calling for instance .. code-block:: c 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. 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 .. code-block:: c 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. 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: .. literalinclude:: ./../../../../../../tools/crc/crc-15_0xc599.c :language: c :linenos: :lines: 71-88 :caption: CRC-15 Polynomial 0xC599 .. literalinclude:: ./../../../../../../tools/crc/crc-10_0x48f.c :language: c :linenos: :lines: 71-88 :caption: CRC-10 Polynomial 0x48F Parameters used to configure the driver --------------------------------------- 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``. 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``. Examples for standard operations -------------------------------- 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 file ``adi_defs.h``. ``ADI_MAX_REGISTER_SIZE_IN_BYTES`` has the value 6. Code example to read Status Register Group C: .. code-block:: 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; } } 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: .. code-block:: c 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: .. code-block:: c 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. 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 files ``adi_ades183x_defs.h`` and ``adi_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: .. code-block:: c 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: .. code-block:: c 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);