*
* @copyright © 2010 - 2026, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
* All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, 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.
*
* We kindly request you to use one or more of the following phrases to refer to
* foxBMS in your hardware, software, documentation or advertising materials:
*
* - "This product uses parts of foxBMS®"
* - "This product includes parts of foxBMS®"
* - "This product is derived from foxBMS®"
*
*/
* @file database.c
* @author foxBMS Team
* @date 2015-08-18 (date of creation)
* @updated 2026-04-20 (date of last update)
* @version v1.11.0
* @ingroup ENGINE
* @prefix DATA
*
* @brief Database module implementation
* @details The database read/write functions put references to the database
* entries to be read/written in the database queue. The function
* DATA_Task() reads the queue to get the database entries to be
* read/written. Up to DATA_MAX_ENTRIES_PER_ACCESS entries can be
* read/written with the same function call. To avoid code
* duplication, the functions to read/write 1 to
* DATA_MAX_ENTRIES_PER_ACCESS-1 entries call the function to
* read/write DATA_MAX_ENTRIES_PER_ACCESS entries and use NULL_PTR
* for the entries that are not to be read/written. DATA_Task()
* checks that the first entry is not a NULL_PTR and asserts if it
* is not the case. If subsequent entries are found in the database
* queue, they are simply ignored if they are NULL_PTR.
*/
#include "general.h"
#include "database.h"
#include "ftask.h"
#include "os.h"
#include <stdint.h>
#include <string.h>
#define DATA_MAX_QUEUE_TIMEOUT_MS (10u)
#define DATA_QUEUE_TIMEOUT_MS (DATA_MAX_QUEUE_TIMEOUT_MS / OS_TICK_RATE_MS)
FAS_STATIC_ASSERT(DATA_QUEUE_TIMEOUT_MS > 0u, "invalid database queue timeout!");
typedef struct {
uint8_t nrDatabaseEntries;
DATA_BASE_s *pDatabase;
} DATA_BASE_HEADER_s;
* @brief device configuration of database
* @details all attributes of device configuration are listed here (pointer to
* channel list, number of channels)
*/
static const DATA_BASE_HEADER_s data_baseHeader = {
.nrDatabaseEntries = sizeof(data_database) / sizeof(DATA_BASE_s),
.pDatabase = &data_database[0],
};
* @brief uniqueId to respective database entry selector
* @details This array is the link between the uniqueId of a database entry and
* the actual position of the database entry in data_database[].
* The IDs are set to its final value, when Data_Initialize is called.
*/
static uint8_t data_uniqueIdToDatabaseEntry[DATA_BLOCK_ID_MAX] = {0};
static void DATA_IterateOverDatabaseEntries(const DATA_QUEUE_MESSAGE_s *kpReceiveMessage);
static STD_RETURN_TYPE_e DATA_AccessDatabaseEntries(
DATA_BLOCK_ACCESS_TYPE_e accessType,
void *pData0,
void *pData1,
void *pData2,
void *pData3);
static void DATA_CopyData(
DATA_BLOCK_ACCESS_TYPE_e accessType,
uint32_t dataLength,
void *pDatabaseStruct,
void *pPassedDataStruct);
static STD_RETURN_TYPE_e DATA_AccessDatabaseEntries(
DATA_BLOCK_ACCESS_TYPE_e accessType,
void *pData0,
void *pData1,
void *pData2,
void *pData3) {
FAS_ASSERT((accessType == DATA_WRITE_ACCESS) || (accessType == DATA_READ_ACCESS));
FAS_ASSERT(pData0 != NULL_PTR);
* is DATA_Read1DataBlock/DATA_Write1DataBlock). The DATA_Task function checks the pointer being not NULL_PTR prior
* to usage. */
* is DATA_Read1DataBlock/DATA_Write1DataBlock or DATA_Read2DataBlocks/DATA_Write2DataBlocks). The DATA_Task
* function checks the pointer being not NULL_PTR prior to usage. */
* is DATA_Read1DataBlock/DATA_Write1DataBlock, DATA_Read2DataBlocks/DATA_Write2DataBlocks or
* DATA_Read3DataBlocks/DATA_Write3DataBlocks). The DATA_Task function checks the pointer being not NULL_PTR prior
* to usage. */
STD_RETURN_TYPE_e retval = STD_NOT_OK;
DATA_QUEUE_MESSAGE_s data_sendMessage = {
.pDatabaseEntry[DATA_ENTRY_0] = pData0,
.pDatabaseEntry[DATA_ENTRY_1] = pData1,
.pDatabaseEntry[DATA_ENTRY_2] = pData2,
.pDatabaseEntry[DATA_ENTRY_3] = pData3,
.accessType = accessType,
};
if (OS_SendToBackOfQueue(ftsk_databaseQueue, (void *)&data_sendMessage, DATA_QUEUE_TIMEOUT_MS) == OS_SUCCESS) {
retval = STD_OK;
}
return retval;
}
static void DATA_CopyData(
DATA_BLOCK_ACCESS_TYPE_e accessType,
uint32_t dataLength,
void *pDatabaseStruct,
void *pPassedDataStruct) {
FAS_ASSERT((accessType == DATA_WRITE_ACCESS) || (accessType == DATA_READ_ACCESS));
FAS_ASSERT(pDatabaseStruct != NULL_PTR);
FAS_ASSERT(pPassedDataStruct != NULL_PTR);
if (accessType == DATA_WRITE_ACCESS) {
* for all database entries. */
DATA_BLOCK_HEADER_s *pHeader = (DATA_BLOCK_HEADER_s *)pPassedDataStruct;
pHeader->previousTimestamp = pHeader->timestamp;
pHeader->timestamp = OS_GetTickCount();
* */
(void)memcpy(pDatabaseStruct, pPassedDataStruct, dataLength);
} else if (accessType == DATA_READ_ACCESS) {
(void)memcpy(pPassedDataStruct, pDatabaseStruct, dataLength);
} else {
FAS_ASSERT(FAS_TRAP);
}
}
static void DATA_IterateOverDatabaseEntries(const DATA_QUEUE_MESSAGE_s *kpReceiveMessage) {
FAS_ASSERT(kpReceiveMessage != NULL_PTR);
for (uint8_t queueEntry = 0u; queueEntry < DATA_MAX_ENTRIES_PER_ACCESS; queueEntry++) {
* All pointers in the array, expect the first one, might be NULL_PTR, which is valid.
* Again, to understand explanation see the comments in DATA_Read1DataBlock, DATA_Read2DataBlocks,
* DATA_Read3DataBlocks and DATA_Read4DataBlocks as well as DATA_Write1DataBlock,
* DATA_Write2DataBlocks, DATA_Write3DataBlocks and DATA_Write4DataBlocks */
if (kpReceiveMessage->pDatabaseEntry[queueEntry] != NULL_PTR) {
void *pPassedDataStruct = kpReceiveMessage->pDatabaseEntry[queueEntry];
DATA_BLOCK_ACCESS_TYPE_e accessType = kpReceiveMessage->accessType;
const DATA_BLOCK_HEADER_s *kpHeader = (DATA_BLOCK_HEADER_s *)kpReceiveMessage->pDatabaseEntry[queueEntry];
uint8_t uniqueId = (uint8_t)(kpHeader->uniqueId);
FAS_ASSERT(uniqueId < (uint8_t)DATA_BLOCK_ID_MAX);
uint8_t entryIndex = data_uniqueIdToDatabaseEntry[uniqueId];
void *pDatabaseStruct = (void *)data_baseHeader.pDatabase[entryIndex].pDatabaseEntry;
uint32_t dataLength = data_baseHeader.pDatabase[entryIndex].dataLength;
DATA_CopyData(accessType, dataLength, pDatabaseStruct, pPassedDataStruct);
}
}
}
STD_RETURN_TYPE_e DATA_Initialize(void) {
STD_RETURN_TYPE_e retval = STD_OK;
const bool allQueuesCreatedCopyForAssert = ftsk_allQueuesCreated;
FAS_ASSERT(allQueuesCreatedCopyForAssert == true);
FAS_STATIC_ASSERT((sizeof(data_database) != 0u), "No database defined");
declared with length DATA_BLOCK_ID_MAX, this assert should never fail */
FAS_STATIC_ASSERT(
((sizeof(data_database) / sizeof(DATA_BASE_s)) == (uint8_t)(DATA_BLOCK_ID_MAX)), "Database array length error");
for (uint16_t i = 0u; i < data_baseHeader.nrDatabaseEntries; i++) {
uint8_t *pStartDatabaseEntryWR = (uint8_t *)data_baseHeader.pDatabase[i].pDatabaseEntry;
for (uint32_t j = 0u; j < (data_baseHeader.pDatabase + i)->dataLength; j++) {
if (j >= sizeof(DATA_BLOCK_ID_e)) {
*pStartDatabaseEntryWR = 0;
}
pStartDatabaseEntryWR++;
}
}
for (uint8_t databaseEntry = 0u; databaseEntry < data_baseHeader.nrDatabaseEntries; databaseEntry++) {
DATA_BLOCK_HEADER_s *pHeader = (DATA_BLOCK_HEADER_s *)data_baseHeader.pDatabase[databaseEntry].pDatabaseEntry;
despite the ID existing) */
FAS_ASSERT(pHeader != NULL_PTR);
DATA_BLOCK_ID_e blockId = pHeader->uniqueId;
if ((blockId < DATA_BLOCK_ID_MAX) && (databaseEntry < DATA_BLOCK_ID_MAX)) {
data_uniqueIdToDatabaseEntry[blockId] = databaseEntry;
} else {
retval = STD_NOT_OK;
}
}
if (ftsk_databaseQueue == NULL_PTR) {
retval = STD_NOT_OK;
}
return retval;
}
void DATA_Task(void) {
if (ftsk_databaseQueue != NULL_PTR) {
DATA_QUEUE_MESSAGE_s receiveMessage = {
.accessType = DATA_READ_ACCESS,
.pDatabaseEntry = {GEN_REPEAT_U(NULL_PTR, GEN_STRIP(DATA_MAX_ENTRIES_PER_ACCESS))}};
if (OS_ReceiveFromQueue(ftsk_databaseQueue, (&receiveMessage), 1u) == OS_SUCCESS) {
* See the comments in DATA_Read1DataBlock, DATA_Read2DataBlocks, DATA_Read3DataBlocks and
* DATA_Read4DataBlocks as well as DATA_Write1DataBlock, DATA_Write2DataBlocks,
* DATA_Write3DataBlocks and DATA_Write4DataBlocks */
FAS_ASSERT(receiveMessage.pDatabaseEntry[0] != NULL_PTR);
DATA_IterateOverDatabaseEntries(&receiveMessage);
}
}
}
void DATA_DummyFunction(void) {
}
STD_RETURN_TYPE_e DATA_Read1DataBlock(void *pDataToReceiver0) {
FAS_ASSERT(pDataToReceiver0 != NULL_PTR);
return DATA_AccessDatabaseEntries(DATA_READ_ACCESS, pDataToReceiver0, NULL_PTR, NULL_PTR, NULL_PTR);
}
STD_RETURN_TYPE_e DATA_Read2DataBlocks(void *pDataToReceiver0, void *pDataToReceiver1) {
FAS_ASSERT(pDataToReceiver0 != NULL_PTR);
FAS_ASSERT(pDataToReceiver1 != NULL_PTR);
return DATA_AccessDatabaseEntries(DATA_READ_ACCESS, pDataToReceiver0, pDataToReceiver1, NULL_PTR, NULL_PTR);
}
STD_RETURN_TYPE_e DATA_Read3DataBlocks(void *pDataToReceiver0, void *pDataToReceiver1, void *pDataToReceiver2) {
FAS_ASSERT(pDataToReceiver0 != NULL_PTR);
FAS_ASSERT(pDataToReceiver1 != NULL_PTR);
FAS_ASSERT(pDataToReceiver2 != NULL_PTR);
return DATA_AccessDatabaseEntries(DATA_READ_ACCESS, pDataToReceiver0, pDataToReceiver1, pDataToReceiver2, NULL_PTR);
}
STD_RETURN_TYPE_e DATA_Read4DataBlocks(
void *pDataToReceiver0,
void *pDataToReceiver1,
void *pDataToReceiver2,
void *pDataToReceiver3) {
FAS_ASSERT(pDataToReceiver0 != NULL_PTR);
FAS_ASSERT(pDataToReceiver1 != NULL_PTR);
FAS_ASSERT(pDataToReceiver2 != NULL_PTR);
FAS_ASSERT(pDataToReceiver3 != NULL_PTR);
return DATA_AccessDatabaseEntries(
DATA_READ_ACCESS, pDataToReceiver0, pDataToReceiver1, pDataToReceiver2, pDataToReceiver3);
}
STD_RETURN_TYPE_e DATA_Write1DataBlock(void *pDataFromSender0) {
FAS_ASSERT(pDataFromSender0 != NULL_PTR);
return DATA_AccessDatabaseEntries(DATA_WRITE_ACCESS, pDataFromSender0, NULL_PTR, NULL_PTR, NULL_PTR);
}
STD_RETURN_TYPE_e DATA_Write2DataBlocks(void *pDataFromSender0, void *pDataFromSender1) {
FAS_ASSERT(pDataFromSender0 != NULL_PTR);
FAS_ASSERT(pDataFromSender1 != NULL_PTR);
return DATA_AccessDatabaseEntries(DATA_WRITE_ACCESS, pDataFromSender0, pDataFromSender1, NULL_PTR, NULL_PTR);
}
STD_RETURN_TYPE_e DATA_Write3DataBlocks(void *pDataFromSender0, void *pDataFromSender1, void *pDataFromSender2) {
FAS_ASSERT(pDataFromSender0 != NULL_PTR);
FAS_ASSERT(pDataFromSender1 != NULL_PTR);
FAS_ASSERT(pDataFromSender2 != NULL_PTR);
return DATA_AccessDatabaseEntries(
DATA_WRITE_ACCESS, pDataFromSender0, pDataFromSender1, pDataFromSender2, NULL_PTR);
}
STD_RETURN_TYPE_e DATA_Write4DataBlocks(
void *pDataFromSender0,
void *pDataFromSender1,
void *pDataFromSender2,
void *pDataFromSender3) {
FAS_ASSERT(pDataFromSender0 != NULL_PTR);
FAS_ASSERT(pDataFromSender1 != NULL_PTR);
FAS_ASSERT(pDataFromSender2 != NULL_PTR);
FAS_ASSERT(pDataFromSender3 != NULL_PTR);
return DATA_AccessDatabaseEntries(
DATA_WRITE_ACCESS, pDataFromSender0, pDataFromSender1, pDataFromSender2, pDataFromSender3);
}
extern void DATA_ExecuteDataBist(void) {
DATA_BLOCK_DUMMY_FOR_SELF_TEST_s dummyWriteTable = {.header.uniqueId = DATA_BLOCK_ID_DUMMY_FOR_SELF_TEST};
dummyWriteTable.member1 = UINT8_MAX;
dummyWriteTable.member2 = DATA_DUMMY_VALUE_UINT8_T_ALTERNATING_BIT_PATTERN;
STD_RETURN_TYPE_e writeReturnValue = DATA_WRITE_DATA(&dummyWriteTable);
FAS_ASSERT(writeReturnValue == STD_OK);
DATA_BLOCK_DUMMY_FOR_SELF_TEST_s dummyReadTable = {.header.uniqueId = DATA_BLOCK_ID_DUMMY_FOR_SELF_TEST};
STD_RETURN_TYPE_e readReturnValue = DATA_READ_DATA(&dummyReadTable);
FAS_ASSERT(readReturnValue == STD_OK);
FAS_ASSERT(dummyReadTable.member1 == dummyWriteTable.member1);
FAS_ASSERT(dummyReadTable.member2 == dummyWriteTable.member2);
}
#ifdef UNITY_UNIT_TEST
extern void TEST_DATA_IterateOverDatabaseEntries(const DATA_QUEUE_MESSAGE_s *kpReceiveMessage) {
DATA_IterateOverDatabaseEntries(kpReceiveMessage);
}
extern STD_RETURN_TYPE_e TEST_DATA_AccessDatabaseEntries(
DATA_BLOCK_ACCESS_TYPE_e accessType,
void *pData0,
void *pData1,
void *pData2,
void *pData3) {
return DATA_AccessDatabaseEntries(accessType, pData0, pData1, pData2, pData3);
}
extern void TEST_DATA_CopyData(
DATA_BLOCK_ACCESS_TYPE_e accessType,
uint32_t dataLength,
void *pDatabaseStruct,
void *pPassedDataStruct) {
DATA_CopyData(accessType, dataLength, pDatabaseStruct, pPassedDataStruct);
}
#endif