From 2d32752febf0c23639f1b481b4810d052ad5bbfc Mon Sep 17 00:00:00 2001 From: cfif Date: Fri, 20 Mar 2026 15:45:18 +0300 Subject: [PATCH] Init --- APP/blf.c | 590 +++++++++++++++++++++++++++++++++++++++++++++++ APP/blf.h | 337 +++++++++++++++++++++++++++ APP/main.c | 125 ++++++++++ APP/modular.json | 12 + CMakeLists.txt | 13 ++ modular.json | 14 ++ 6 files changed, 1091 insertions(+) create mode 100644 APP/blf.c create mode 100644 APP/blf.h create mode 100644 APP/main.c create mode 100644 APP/modular.json create mode 100644 CMakeLists.txt create mode 100644 modular.json diff --git a/APP/blf.c b/APP/blf.c new file mode 100644 index 0000000..7258b98 --- /dev/null +++ b/APP/blf.c @@ -0,0 +1,590 @@ +/** + * @file blf.c + * @brief Реализация функций для создания BLF-файлов. + * @details Содержит все функции, объявленные в blf.h. Не использует динамическую память. + * Поддерживает stdio и FatFS через условную компиляцию (USE_FATFS). + * + * @author (Ваше имя) + * @date 2026-03-20 + */ + +#include "blf.h" +#include + +/* ------------------------------------------------------------------------- + * Вспомогательные функции файлового ввода-вывода (обёртки над stdio/FatFS) + * ------------------------------------------------------------------------- */ + +/* Открытие файла на запись (создание/перезапись) */ +static int blf_file_open(BLFContext *ctx, const char *filename) { +#ifdef USE_FATFS + FRESULT res = f_open(&ctx->fp, filename, FA_WRITE | FA_CREATE_ALWAYS); + if (res != FR_OK) { + BLF_ERROR_PRINTF("ERROR: FatFS f_open failed with code %d\n", res); + return -1; + } + return 0; +#else + ctx->fp = fopen(filename, "w+b"); + if (ctx->fp == NULL) { + BLF_ERROR_PRINTF("ERROR: Failed to open file %s\n", filename); + return -1; + } + return 0; +#endif +} + +/* Запись блока данных */ +static int blf_file_write(BLFContext *ctx, const void *ptr, size_t size) { +#ifdef USE_FATFS + UINT bw; + FRESULT res = f_write(&ctx->fp, ptr, size, &bw); + if (res != FR_OK || bw != size) { + BLF_ERROR_PRINTF("ERROR: FatFS f_write failed (res=%d, bw=%u, size=%u)\n", + res, bw, (unsigned)size); + return -1; + } + return 0; +#else + if (fwrite(ptr, 1, size, ctx->fp) != size) { + BLF_ERROR_PRINTF("ERROR: fwrite failed (size=%u)\n", (unsigned)size); + return -1; + } + return 0; +#endif +} + +/* Получение текущей позиции в файле */ +static long blf_file_tell(BLFContext *ctx) { +#ifdef USE_FATFS + long pos = (long) f_tell(&ctx->fp); + if (pos < 0) { + BLF_ERROR_PRINTF("ERROR: FatFS f_tell failed\n"); + } + return pos; +#else + long pos = ftell(ctx->fp); + if (pos < 0) { + BLF_ERROR_PRINTF("ERROR: ftell failed\n"); + } + return pos; +#endif +} + +/* Установка позиции в файле (от начала) */ +static int blf_file_seek(BLFContext *ctx, long offset) { +#ifdef USE_FATFS + FRESULT res = f_lseek(&ctx->fp, offset); + if (res != FR_OK) { + BLF_ERROR_PRINTF("ERROR: FatFS f_lseek to %ld failed\n", offset); + return -1; + } + return 0; +#else + if (fseek(ctx->fp, offset, SEEK_SET) != 0) { + BLF_ERROR_PRINTF("ERROR: fseek to %ld failed\n", offset); + return -1; + } + return 0; +#endif +} + +/* Перемещение в конец файла */ +static int blf_file_seek_end(BLFContext *ctx) { +#ifdef USE_FATFS + FRESULT res = f_lseek(&ctx->fp, f_size(&ctx->fp)); + if (res != FR_OK) { + BLF_ERROR_PRINTF("ERROR: FatFS f_lseek to end failed\n"); + return -1; + } + return 0; +#else + if (fseek(ctx->fp, 0, SEEK_END) != 0) { + BLF_ERROR_PRINTF("ERROR: fseek to end failed\n"); + return -1; + } + return 0; +#endif +} + +/* Закрытие файла */ +static int blf_file_close(BLFContext *ctx) { +#ifdef USE_FATFS + FRESULT res = f_close(&ctx->fp); + if (res != FR_OK) { + BLF_ERROR_PRINTF("ERROR: FatFS f_close failed\n"); + return -1; + } + return 0; +#else + if (fclose(ctx->fp) != 0) { + BLF_ERROR_PRINTF("ERROR: fclose failed\n"); + return -1; + } + return 0; +#endif +} + +/* ------------------------------------------------------------------------- + * Вспомогательная функция: добавить миллисекунды к SYSTEMTIME + * ------------------------------------------------------------------------- */ +static void systemtime_add_ms(SYSTEMTIME *st, uint64_t ms) { + uint64_t total_ms = st->milliseconds + ms; + st->milliseconds = total_ms % 1000; + uint64_t carry_seconds = total_ms / 1000; + + uint64_t total_seconds = st->second + carry_seconds; + st->second = total_seconds % 60; + uint64_t carry_minutes = total_seconds / 60; + + uint64_t total_minutes = st->minute + carry_minutes; + st->minute = total_minutes % 60; + uint64_t carry_hours = total_minutes / 60; + + uint64_t total_hours = st->hour + carry_hours; + st->hour = total_hours % 24; + /* Дни не меняются – предполагаем, что запись укладывается в один день */ +} + +/* ------------------------------------------------------------------------- + * Реализация API-функций + * ------------------------------------------------------------------------- */ + +int blf_open(BLFContext *ctx, const char *filename) { + memset(ctx, 0, sizeof(*ctx)); + + if (blf_file_open(ctx, filename) != 0) { + return -1; + } + + /* Инициализация заголовка */ + ctx->header.signature = BL_FILE_SIGNATURE; + ctx->header.statisticsSize = sizeof(FileHeader); + ctx->header.apiNumber = 4070100; + ctx->header.applicationId = 0; + ctx->header.compressionLevel = 0; + ctx->header.applicationMajor = 0; + ctx->header.applicationMinor = 0; + ctx->header.applicationBuild = 0; + ctx->header.fileSize = 0; + ctx->header.uncompressedFileSize = 0; + ctx->header.objectCount = 0; + ctx->header.restorePointsOffset = 0; + + /* Время начала измерения */ + ctx->header.measurementStartTime.year = 2026; + ctx->header.measurementStartTime.month = 3; + ctx->header.measurementStartTime.dayOfWeek = 3; + ctx->header.measurementStartTime.day = 18; + ctx->header.measurementStartTime.hour = 12; + ctx->header.measurementStartTime.minute = 0; + ctx->header.measurementStartTime.second = 0; + ctx->header.measurementStartTime.milliseconds = 0; + + memset(&ctx->header.lastObjectTime, 0, sizeof(SYSTEMTIME)); + + /* Запись заголовка в начало файла */ + ctx->headerPos = blf_file_tell(ctx); + if (ctx->headerPos < 0) { + blf_file_close(ctx); + return -1; + } + if (blf_file_write(ctx, &ctx->header, sizeof(FileHeader)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write file header\n"); + blf_file_close(ctx); + return -1; + } + + ctx->objectCount = 0; + ctx->maxTimestamp = 0; + ctx->in_container = 0; + return 0; +} + +int blf_start_container(BLFContext *ctx, uint64_t timestamp) { + if (!ctx) { + BLF_ERROR_PRINTF("ERROR: blf_start_container: null context\n"); + return -1; + } + if (ctx->in_container) { + BLF_ERROR_PRINTF("ERROR: Already inside a container (nested containers not supported)\n"); + return -1; + } + + /* Подготовка заголовка контейнера */ + ctx->container_hdr.base.mSignature = BL_OBJ_SIGNATURE; + ctx->container_hdr.base.mHeaderSize = sizeof(VBLObjectHeaderBase); + ctx->container_hdr.base.mHeaderVersion = 1; + ctx->container_hdr.base.mObjectSize = 0; /* будет заполнено позже */ + ctx->container_hdr.base.mObjectType = BL_OBJ_TYPE_LOG_CONTAINER; + + memset(&ctx->container_hdr.data, 0, sizeof(LogContainerData)); + ctx->container_hdr.data.compressionMethod = 0; + + /* Запись заголовка контейнера */ + ctx->container_hdr_pos = blf_file_tell(ctx); + if (ctx->container_hdr_pos < 0) { + return -1; + } + if (blf_file_write(ctx, &ctx->container_hdr, sizeof(ContainerHeader)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write container header\n"); + return -1; + } + + ctx->container_timestamp = timestamp; + ctx->in_container = 1; + return 0; +} + +int blf_end_container(BLFContext *ctx) { + if (!ctx) { + BLF_ERROR_PRINTF("ERROR: blf_end_container: null context\n"); + return -1; + } + if (!ctx->in_container) { + BLF_ERROR_PRINTF("ERROR: No open container to end\n"); + return -1; + } + + /* Вычисляем размер данных внутри контейнера */ + long current_pos = blf_file_tell(ctx); + if (current_pos < 0) return -1; + long data_size = current_pos - (ctx->container_hdr_pos + sizeof(ContainerHeader)); + if (data_size < 0) { + BLF_ERROR_PRINTF("ERROR: Negative container data size\n"); + return -1; + } + + /* Обновляем заголовок контейнера */ + ctx->container_hdr.base.mObjectSize = sizeof(ContainerHeader) + data_size; + ctx->container_hdr.data.uncompressedFileSize = (uint32_t)data_size; + + /* Перезаписываем заголовок */ + if (blf_file_seek(ctx, ctx->container_hdr_pos) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to seek to container header\n"); + return -1; + } + if (blf_file_write(ctx, &ctx->container_hdr, sizeof(ContainerHeader)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to rewrite container header\n"); + return -1; + } + + /* Возвращаемся в конец файла */ + if (blf_file_seek_end(ctx) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to seek to end of file after container\n"); + return -1; + } + + ctx->objectCount++; + if (ctx->container_timestamp > ctx->maxTimestamp) + ctx->maxTimestamp = ctx->container_timestamp; + + ctx->in_container = 0; + return 0; +} + +int blf_add_can_message(BLFContext *ctx, + uint16_t channel, + uint32_t id, + uint8_t flags, + uint8_t dlc, + const uint8_t *data, + uint64_t timestamp) { + if (!ctx) { + BLF_ERROR_PRINTF("ERROR: blf_add_can_message: null context\n"); + return -1; + } + if (dlc > 8) { + BLF_ERROR_PRINTF("ERROR: CAN message DLC > 8\n"); + return -1; + } + + VBLObjectHeader objHdr; + VBLCANMessage canMsg; + + canMsg.mChannel = channel; + canMsg.mFlags = flags; + canMsg.mDLC = dlc; + canMsg.mID = id; + if (data && dlc <= 8) { + memcpy(canMsg.mData, data, dlc); + if (dlc < 8) memset(canMsg.mData + dlc, 0, 8 - dlc); + } else { + memset(canMsg.mData, 0, 8); + } + + objHdr.mBase.mSignature = BL_OBJ_SIGNATURE; + objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader); + objHdr.mBase.mHeaderVersion = 1; + objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLCANMessage); + objHdr.mBase.mObjectType = BL_OBJ_TYPE_CAN_MESSAGE; + objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS; + objHdr.mClientIndex = 0; + objHdr.mObjectVersion = 0; + objHdr.mObjectTimeStamp = timestamp; + + if (blf_file_write(ctx, &objHdr, sizeof(objHdr)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write CAN message header\n"); + return -1; + } + if (blf_file_write(ctx, &canMsg, sizeof(canMsg)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write CAN message body\n"); + return -1; + } + + if (!ctx->in_container) ctx->objectCount++; + if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp; + return 0; +} + +int blf_add_lin_message_obsolete(BLFContext *ctx, + uint16_t channel, + uint8_t id, + uint8_t dlc, + const uint8_t *data, + uint8_t dir, + uint64_t timestamp, + uint16_t checksum) { + if (!ctx) { + BLF_ERROR_PRINTF("ERROR: blf_add_lin_message_obsolete: null context\n"); + return -1; + } + if (dlc > 8) { + BLF_ERROR_PRINTF("ERROR: LIN message DLC > 8\n"); + return -1; + } + + VBLObjectHeader objHdr; + VBLLINMessage linMsg; + memset(&linMsg, 0, sizeof(linMsg)); + + linMsg.mChannel = channel; + linMsg.mID = id & 0x3F; + linMsg.mDLC = dlc; + if (data && dlc > 0) memcpy(linMsg.mData, data, dlc); + + uint8_t header_bits = 34; /* синхроразрыв + синхрополе + идентификатор */ + uint8_t response_bits = (dlc + 1) * 10; /* данные + контрольная сумма */ + linMsg.mHeaderTime = header_bits; + linMsg.mFullTime = header_bits + response_bits; + + linMsg.mCRC = checksum; + linMsg.mDir = dir; + linMsg.mReserved = 0; + + objHdr.mBase.mSignature = BL_OBJ_SIGNATURE; + objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader); + objHdr.mBase.mHeaderVersion = 1; + objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLLINMessage); + objHdr.mBase.mObjectType = BL_OBJ_TYPE_LIN_MESSAGE; + objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS; + objHdr.mClientIndex = 0; + objHdr.mObjectVersion = 0; + objHdr.mObjectTimeStamp = timestamp; + + if (blf_file_write(ctx, &objHdr, sizeof(objHdr)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write LIN message header\n"); + return -1; + } + if (blf_file_write(ctx, &linMsg, sizeof(linMsg)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write LIN message body\n"); + return -1; + } + + if (!ctx->in_container) ctx->objectCount++; + if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp; + return 0; +} + +int blf_add_lin_send_error(BLFContext *ctx, + uint16_t channel, + uint8_t id, + uint8_t dlc, + uint64_t timestamp) { + if (!ctx) { + BLF_ERROR_PRINTF("ERROR: blf_add_lin_send_error: null context\n"); + return -1; + } + + VBLObjectHeader objHdr; + VBLLINSendError sendErr; + + memset(&sendErr, 0, sizeof(sendErr)); + sendErr.mChannel = channel; + sendErr.mID = id & 0x3F; + sendErr.mDLC = dlc; + sendErr.mHeaderTime = 34; + sendErr.mFullTime = 34; + + objHdr.mBase.mSignature = BL_OBJ_SIGNATURE; + objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader); + objHdr.mBase.mHeaderVersion = 1; + objHdr.mBase.mObjectSize = sizeof(VBLObjectHeader) + sizeof(VBLLINSendError); + objHdr.mBase.mObjectType = BL_OBJ_TYPE_LIN_SND_ERROR; + objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS; + objHdr.mClientIndex = 0; + objHdr.mObjectVersion = 0; + objHdr.mObjectTimeStamp = timestamp; + + if (blf_file_write(ctx, &objHdr, sizeof(objHdr)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write LIN send error header\n"); + return -1; + } + if (blf_file_write(ctx, &sendErr, sizeof(sendErr)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write LIN send error body\n"); + return -1; + } + + if (!ctx->in_container) ctx->objectCount++; + if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp; + return 0; +} + +int blf_add_env_data(BLFContext *ctx, + const char *name, + const uint8_t *data, + uint32_t data_len, + uint64_t timestamp) { + if (!ctx) { + BLF_ERROR_PRINTF("ERROR: blf_add_env_data: null context\n"); + return -1; + } + if (data == NULL && data_len > 0) { + BLF_ERROR_PRINTF("ERROR: blf_add_env_data: data is NULL but data_len > 0\n"); + return -1; + } + + uint32_t name_len = (name != NULL) ? (uint32_t)strlen(name) : 0; + uint32_t object_size = sizeof(VBLObjectHeader) + sizeof(uint32_t) * 2 + sizeof(uint64_t) + name_len + data_len; + + VBLObjectHeader objHdr; + objHdr.mBase.mSignature = BL_OBJ_SIGNATURE; + objHdr.mBase.mHeaderSize = sizeof(VBLObjectHeader); + objHdr.mBase.mHeaderVersion = 1; + objHdr.mBase.mObjectSize = object_size; + objHdr.mBase.mObjectType = BL_OBJ_TYPE_ENV_DATA; + objHdr.mObjectFlags = BL_OBJ_FLAG_TIME_ONE_NANS; + objHdr.mClientIndex = 0; + objHdr.mObjectVersion = 0; + objHdr.mObjectTimeStamp = timestamp; + + if (blf_file_write(ctx, &objHdr, sizeof(objHdr)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA header\n"); + return -1; + } + + uint32_t nameLength = name_len; + uint32_t dataLength = data_len; + uint64_t reserved = 0; + if (blf_file_write(ctx, &nameLength, sizeof(nameLength)) != 0 || + blf_file_write(ctx, &dataLength, sizeof(dataLength)) != 0 || + blf_file_write(ctx, &reserved, sizeof(reserved)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA fields\n"); + return -1; + } + + if (name_len > 0 && name != NULL) { + if (blf_file_write(ctx, name, name_len) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA name\n"); + return -1; + } + } + + if (data_len > 0 && data != NULL) { + if (blf_file_write(ctx, data, data_len) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA data\n"); + return -1; + } + } + + /* Выравнивание до 4 байт (требование формата) */ + long pos = blf_file_tell(ctx); + if (pos < 0) return -1; + long pad = (4 - (pos % 4)) % 4; + for (long i = 0; i < pad; i++) { + uint8_t zero = 0; + if (blf_file_write(ctx, &zero, 1) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to write ENV_DATA padding\n"); + return -1; + } + } + + if (!ctx->in_container) ctx->objectCount++; + if (timestamp > ctx->maxTimestamp) ctx->maxTimestamp = timestamp; + return 0; +} + +/* Удобные обёртки (преобразование времени) */ +int blf_add_can_message_struct(BLFContext *ctx, const CanMessageStruct *msg) { + uint64_t ts_ns = (uint64_t)msg->timestamp * 1000000ULL; + return blf_add_can_message(ctx, msg->channel, msg->id, msg->flags, msg->dlc, msg->data, ts_ns); +} + +int blf_add_lin_message_struct(BLFContext *ctx, const LinMessageStruct *msg) { + uint64_t ts_ns = (uint64_t)msg->timestamp * 1000000ULL; + return blf_add_lin_message_obsolete(ctx, msg->channel, msg->id, msg->dlc, msg->data, msg->dir, ts_ns, msg->checksum); +} + +int blf_add_lin_send_error_struct(BLFContext *ctx, const LinSendErrorStruct *err) { + uint64_t ts_ns = (uint64_t)err->timestamp * 1000000ULL; + return blf_add_lin_send_error(ctx, err->channel, err->id, err->dlc, ts_ns); +} + +int blf_add_env_data_struct(BLFContext *ctx, const EnvDataStruct *env) { + uint64_t ts_ns = (uint64_t)env->timestamp * 1000000ULL; + return blf_add_env_data(ctx, env->name, env->data, env->data_len, ts_ns); +} + +int blf_close(BLFContext *ctx) { + if (!ctx) { + BLF_ERROR_PRINTF("ERROR: blf_close: null context\n"); + return -1; + } + + /* Закрываем незавершённый контейнер, если есть */ + if (ctx->in_container) { + if (blf_end_container(ctx) != 0) { + BLF_ERROR_PRINTF("WARNING: Failed to close remaining container\n"); + } + } + + /* Выравнивание файла до 4 байт */ + long endPos = blf_file_tell(ctx); + if (endPos < 0) { + blf_file_close(ctx); + return -1; + } + long padding = (4 - (endPos % 4)) % 4; + for (long i = 0; i < padding; i++) { + uint8_t zero = 0; + if (blf_file_write(ctx, &zero, 1) != 0) { + blf_file_close(ctx); + return -1; + } + } + endPos += padding; + + /* Вычисляем время последнего объекта */ + ctx->header.lastObjectTime = ctx->header.measurementStartTime; + uint64_t total_ms = ctx->maxTimestamp / 1000000; + systemtime_add_ms(&ctx->header.lastObjectTime, total_ms); + + /* Обновляем поля заголовка файла */ + ctx->header.fileSize = (uint64_t)endPos; + ctx->header.uncompressedFileSize = (uint64_t)endPos; + ctx->header.objectCount = ctx->objectCount; + + /* Перезаписываем заголовок */ + if (blf_file_seek(ctx, ctx->headerPos) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to seek to file header\n"); + blf_file_close(ctx); + return -1; + } + if (blf_file_write(ctx, &ctx->header, sizeof(FileHeader)) != 0) { + BLF_ERROR_PRINTF("ERROR: Failed to rewrite file header\n"); + blf_file_close(ctx); + return -1; + } + + return blf_file_close(ctx); +} \ No newline at end of file diff --git a/APP/blf.h b/APP/blf.h new file mode 100644 index 0000000..3f25267 --- /dev/null +++ b/APP/blf.h @@ -0,0 +1,337 @@ +/** + * @file blf.h + * @brief Заголовочный файл библиотеки для создания BLF-файлов (Vector). + * @details Определяет структуры, константы и функции для работы с форматом BLF. + * Поддерживает stdio (ПК) и FatFS (встраиваемые системы) через условную компиляцию. + * Не использует динамическую память – контекст выделяется пользователем. + * + * @author (Ваше имя) + * @date 2026-03-20 + */ + +#ifndef BLF_H +#define BLF_H + +#include + +/* ------------------------------------------------------------------------- + * Обёртка для вывода ошибок (может быть переопределена до включения заголовка) + * ------------------------------------------------------------------------- */ +#ifndef BLF_ERROR_PRINTF +#include +#define BLF_ERROR_PRINTF(...) printf(__VA_ARGS__) +#endif + +/* ------------------------------------------------------------------------- + * Константы формата BLF (сигнатуры, типы объектов, флаги) + * ------------------------------------------------------------------------- */ +#define BL_FILE_SIGNATURE 0x47474F4Cu /* "LOGG" – сигнатура файла */ +#define BL_OBJ_SIGNATURE 0x4A424F4Cu /* "LOBJ" – сигнатура объекта */ +#define BL_OBJ_TYPE_CAN_MESSAGE 1 /* CAN-сообщение */ +#define BL_OBJ_TYPE_LIN_MESSAGE 11 /* LIN-сообщение */ +#define BL_OBJ_TYPE_LIN_SND_ERROR 15 /* Ошибка отправки LIN */ +#define BL_OBJ_TYPE_LOG_CONTAINER 10 /* Контейнер */ +#define BL_OBJ_TYPE_ENV_DATA 9 /* Данные окружения (ENV_DATA) */ +#define BL_OBJ_FLAG_TIME_ONE_NANS 0x00000002 /* Временная метка в наносекундах */ + +/* Флаги CAN-сообщения: бит 7 = RTR, биты 0-3 = направление */ +#define CAN_MSG_FLAGS(dir, rtr) \ + ((uint8_t)(((uint8_t)(rtr & 0x01) << 7) | ((uint8_t)(dir & 0x0F)))) +#define CAN_DIR_TX 1 /* передача */ +#define CAN_DIR_RX 0 /* приём */ + +/* Направления LIN-сообщений */ +#define LIN_DIR_RX 0 +#define LIN_DIR_TX 1 + +/* ------------------------------------------------------------------------- + * Упакованные структуры BLF (соответствуют двоичному формату Vector) + * ------------------------------------------------------------------------- */ +#if defined(_MSC_VER) +#pragma pack(push, 1) +#else +#define PACKED __attribute__((packed)) +#endif + +/* Системное время (используется в заголовке файла) */ +typedef struct PACKED { + uint16_t year; /* год */ + uint16_t month; /* месяц (1–12) */ + uint16_t dayOfWeek; /* день недели (0–6, 0 = воскресенье) */ + uint16_t day; /* день месяца */ + uint16_t hour; /* часы (0–23) */ + uint16_t minute; /* минуты (0–59) */ + uint16_t second; /* секунды (0–59) */ + uint16_t milliseconds; /* миллисекунды (0–999) */ +} SYSTEMTIME; + +/** + * @brief Заголовок BLF-файла (структура FileStatistics). + * @details Размер 144 байта. Содержит общую информацию о файле. + */ +typedef struct PACKED { + uint32_t signature; /* "LOGG" */ + uint32_t statisticsSize; /* sizeof(FileHeader) = 144 */ + uint32_t apiNumber; /* версия API, например 4070100 */ + uint8_t applicationId; /* 1 = CANalyzer/CANoe */ + uint8_t compressionLevel; /* 0 – без сжатия */ + uint8_t applicationMajor; /* старшая версия приложения */ + uint8_t applicationMinor; /* младшая версия приложения */ + uint64_t fileSize; /* полный размер файла (с выравниванием) */ + uint64_t uncompressedFileSize; /* размер несжатых данных */ + uint32_t objectCount; /* количество объектов верхнего уровня */ + uint32_t applicationBuild; /* номер сборки приложения */ + SYSTEMTIME measurementStartTime; /* время начала записи */ + SYSTEMTIME lastObjectTime; /* время последнего объекта */ + uint64_t restorePointsOffset; /* смещение до точек восстановления (0) */ + uint32_t reservedFileStatistics[16]; /* зарезервировано */ +} FileHeader; + +/* Базовый заголовок объекта (16 байт) */ +typedef struct PACKED { + uint32_t mSignature; /* "LOBJ" */ + uint16_t mHeaderSize; /* размер полного заголовка (обычно sizeof(VBLObjectHeader)) */ + uint16_t mHeaderVersion; /* версия заголовка (1) */ + uint32_t mObjectSize; /* полный размер объекта (заголовок + данные) */ + uint32_t mObjectType; /* тип объекта */ +} VBLObjectHeaderBase; + +/* Полный заголовок объекта версии 1 (32 байта) */ +typedef struct PACKED { + VBLObjectHeaderBase mBase; /* базовые поля */ + uint32_t mObjectFlags; /* флаги (например, временная метка в наносекундах) */ + uint16_t mClientIndex; /* индекс клиента (обычно 0) */ + uint16_t mObjectVersion; /* версия данных объекта (0) */ + uint64_t mObjectTimeStamp; /* временная метка (наносекунды) */ +} VBLObjectHeader; + +/* Данные CAN-сообщения (16 байт) */ +typedef struct PACKED { + uint16_t mChannel; /* номер канала */ + uint8_t mFlags; /* бит 7 = RTR, остальные – направление */ + uint8_t mDLC; /* длина данных (0-8) */ + uint32_t mID; /* идентификатор */ + uint8_t mData[8]; /* данные */ +} VBLCANMessage; + +/* Данные LIN-сообщения (устаревший тип, 20 байт) */ +typedef struct PACKED { + uint16_t mChannel; + uint8_t mID; /* идентификатор (6 бит) */ + uint8_t mDLC; + uint8_t mData[8]; + uint8_t mFSMId; /* всегда 0 */ + uint8_t mFSMState; /* всегда 0 */ + uint8_t mHeaderTime; /* длительность заголовка в битовых временах */ + uint8_t mFullTime; /* длительность всего кадра в битовых временах */ + uint16_t mCRC; /* контрольная сумма */ + uint8_t mDir; /* направление */ + uint8_t mReserved; +} VBLLINMessage; + +/* Данные ошибки LIN (отсутствие ответа, 10 байт) */ +typedef struct PACKED { + uint16_t mChannel; + uint8_t mID; + uint8_t mDLC; + uint8_t mFSMId; + uint8_t mFSMState; + uint8_t mHeaderTime; + uint8_t mFullTime; +} VBLLINSendError; + +/* Дополнительные данные контейнера (тип 10) */ +typedef struct PACKED { + uint16_t compressionMethod; /* 0 – без сжатия */ + uint16_t reserved1; + uint32_t reserved2; + uint32_t uncompressedFileSize; /* размер данных внутри контейнера */ + uint32_t reserved3; +} LogContainerData; + +/* Полный заголовок контейнера (базовый + данные) */ +typedef struct PACKED { + VBLObjectHeaderBase base; + LogContainerData data; +} ContainerHeader; + +/* ------------------------------------------------------------------------- + * Удобные структуры для передачи данных (в миллисекундах) + * ------------------------------------------------------------------------- */ +typedef struct PACKED { + uint8_t channel; + uint32_t id; + uint8_t flags; + uint8_t dlc; + uint8_t data[8]; + uint32_t timestamp; /* в миллисекундах */ +} CanMessageStruct; + +typedef struct PACKED { + uint8_t channel; + uint8_t id; + uint8_t dlc; + uint8_t data[8]; + uint8_t dir; + uint32_t timestamp; /* в миллисекундах */ + uint16_t checksum; +} LinMessageStruct; + +typedef struct PACKED { + uint8_t channel; + uint8_t id; + uint8_t dlc; + uint32_t timestamp; /* в миллисекундах */ +} LinSendErrorStruct; + +typedef struct PACKED { + char name[4]; /* имя переменной (до 3 символов) */ + const uint8_t* data; + uint32_t data_len; + uint32_t timestamp; /* в миллисекундах */ +} EnvDataStruct; + + +/* Возврат к стандартному выравниванию */ +#if defined(_MSC_VER) +#pragma pack(pop) +#else +#undef PACKED +#endif + + +/* ------------------------------------------------------------------------- + * Контекст BLF-файла (полное определение – для выделения на стеке) + * ------------------------------------------------------------------------- */ +/* Условное включение дескриптора файла в зависимости от USE_FATFS */ +#ifdef USE_FATFS +#include "ff.h" /* FatFS */ + typedef FIL BlfFileHandle; +#else +#include +typedef FILE* BlfFileHandle; +#endif + +typedef struct { + BlfFileHandle fp; /* дескриптор файла */ + FileHeader header; /* заголовок файла (будет перезаписан в конце) */ + long headerPos; /* позиция заголовка в файле */ + int objectCount; /* количество объектов верхнего уровня */ + uint64_t maxTimestamp; /* максимальная временная метка (для lastObjectTime) */ + int in_container; /* флаг: внутри контейнера */ + ContainerHeader container_hdr; /* заголовок открытого контейнера */ + long container_hdr_pos; /* позиция заголовка контейнера в файле */ + uint64_t container_timestamp; /* временная метка контейнера */ +} BLFContext; + +/* ------------------------------------------------------------------------- + * Прототипы функций API + * ------------------------------------------------------------------------- */ + +/** + * @brief Инициализирует BLF-контекст и создаёт новый файл. + * @param ctx Указатель на предварительно выделенный контекст. + * @param filename Имя файла. + * @return 0 при успехе, -1 при ошибке. + */ +int blf_open(BLFContext *ctx, const char *filename); + +/** + * @brief Начинает новый контейнер (объект типа LOG_CONTAINER). + * @param ctx Контекст. + * @param timestamp Временная метка контейнера (наносекунды). + * @return 0 при успехе, -1 при ошибке. + */ +int blf_start_container(BLFContext *ctx, uint64_t timestamp); + +/** + * @brief Завершает текущий контейнер, обновляя его заголовок. + * @param ctx Контекст. + * @return 0 при успехе, -1 при ошибке. + */ +int blf_end_container(BLFContext *ctx); + +/** + * @brief Добавляет CAN-сообщение. + * @param ctx Контекст. + * @param channel Номер канала. + * @param id Идентификатор. + * @param flags Флаги (направление, RTR). + * @param dlc Длина данных (0-8). + * @param data Данные (если NULL, заполняет нулями). + * @param timestamp Временная метка в наносекундах. + * @return 0 при успехе, -1 при ошибке. + */ +int blf_add_can_message(BLFContext *ctx, + uint16_t channel, + uint32_t id, + uint8_t flags, + uint8_t dlc, + const uint8_t *data, + uint64_t timestamp); + +/** + * @brief Добавляет LIN-сообщение (устаревший тип). + * @param ctx Контекст. + * @param channel Номер канала. + * @param id Идентификатор (6 бит). + * @param dlc Длина данных. + * @param data Данные. + * @param dir Направление (LIN_DIR_RX / LIN_DIR_TX). + * @param timestamp Временная метка в наносекундах. + * @param checksum Контрольная сумма. + * @return 0 при успехе, -1 при ошибке. + */ +int blf_add_lin_message_obsolete(BLFContext *ctx, + uint16_t channel, + uint8_t id, + uint8_t dlc, + const uint8_t *data, + uint8_t dir, + uint64_t timestamp, + uint16_t checksum); + +/** + * @brief Добавляет событие отсутствия ответа LIN. + * @param ctx Контекст. + * @param channel Номер канала. + * @param id Идентификатор. + * @param dlc Ожидаемая длина данных. + * @param timestamp Временная метка в наносекундах. + * @return 0 при успехе, -1 при ошибке. + */ +int blf_add_lin_send_error(BLFContext *ctx, + uint16_t channel, + uint8_t id, + uint8_t dlc, + uint64_t timestamp); + +/** + * @brief Добавляет данные окружения (ENV_DATA). + * @param ctx Контекст. + * @param name Имя переменной (строка). + * @param data Указатель на данные. + * @param data_len Длина данных в байтах. + * @param timestamp Временная метка в наносекундах. + * @return 0 при успехе, -1 при ошибке. + */ +int blf_add_env_data(BLFContext *ctx, + const char *name, + const uint8_t *data, + uint32_t data_len, + uint64_t timestamp); + +/* Удобные обёртки, принимающие структуры с временем в миллисекундах */ +int blf_add_can_message_struct(BLFContext *ctx, const CanMessageStruct *msg); +int blf_add_lin_message_struct(BLFContext *ctx, const LinMessageStruct *msg); +int blf_add_lin_send_error_struct(BLFContext *ctx, const LinSendErrorStruct *err); +int blf_add_env_data_struct(BLFContext *ctx, const EnvDataStruct *env); + +/** + * @brief Закрывает BLF-файл, обновляет заголовок. + * @param ctx Контекст. + * @return 0 при успехе, -1 при ошибке. + */ +int blf_close(BLFContext *ctx); + +#endif /* BLF_H */ \ No newline at end of file diff --git a/APP/main.c b/APP/main.c new file mode 100644 index 0000000..f2fb6a3 --- /dev/null +++ b/APP/main.c @@ -0,0 +1,125 @@ +/** + * @file main.c + * @brief Пример использования библиотеки blf для создания BLF-файла. + * @details Демонстрирует создание контейнера с CAN, LIN, LIN Error и ENV_DATA объектами. + * Контекст создаётся на стеке (без динамической памяти). + * + * Компиляция для обычного ПК: + * gcc -o create_blf main.c blf.c -std=c99 -D_GNU_SOURCE + * Компиляция для встраиваемой системы с FatFS: + * arm-none-eabi-gcc -DUSE_FATFS -Ipath/to/fatfs -o create_blf main.c blf.c + * + * @author (Ваше имя) + * @date 2026-03-20 + */ + +#include "blf.h" + +/* Вспомогательная структура для хранения семплов АЦП (используется только в примере) */ +typedef struct { + uint16_t channel1; + uint16_t channel2; + uint16_t channel3; + uint16_t channel4; + uint16_t channel5; +} AdcSample; + +int main() { + BLFContext ctx; /* контекст на стеке – без malloc */ + int ret; + + /* 1. Открываем файл (создаём новый) */ + ret = blf_open(&ctx, "log.blf"); + if (ret != 0) { + BLF_ERROR_PRINTF("Failed to open file, exiting.\n"); + return 1; + } + + /* 2. Начинаем контейнер с временной меткой 1 секунда (1e9 нс) */ + if (blf_start_container(&ctx, 1000000000ULL) != 0) { + BLF_ERROR_PRINTF("Failed to start container\n"); + blf_close(&ctx); + return 1; + } + + /* 3. Добавляем CAN-сообщение */ + CanMessageStruct canMsg = { + .channel = 2, + .id = 0x789, + .flags = CAN_MSG_FLAGS(CAN_DIR_TX, 0), + .dlc = 8, + .data = {0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22}, + .timestamp = 9000 /* 9 секунд (миллисекунды) */ + }; + if (blf_add_can_message_struct(&ctx, &canMsg) != 0) { + BLF_ERROR_PRINTF("Failed to add CAN message\n"); + blf_close(&ctx); + return 1; + } + + /* 4. Добавляем LIN-сообщение */ + LinMessageStruct linMsg = { + .channel = 1, + .id = 0x45, + .dlc = 4, + .data = {0xAA, 0xBB, 0xCC, 0xDD}, + .dir = LIN_DIR_TX, + .timestamp = 9100, + .checksum = 0x5678 + }; + if (blf_add_lin_message_struct(&ctx, &linMsg) != 0) { + BLF_ERROR_PRINTF("Failed to add LIN message\n"); + blf_close(&ctx); + return 1; + } + + /* 5. Добавляем LIN-ошибку отсутствия ответа */ + LinSendErrorStruct sendErr = { + .channel = 1, + .id = 0x12, + .dlc = 8, + .timestamp = 9200 + }; + if (blf_add_lin_send_error_struct(&ctx, &sendErr) != 0) { + BLF_ERROR_PRINTF("Failed to add LIN send error\n"); + blf_close(&ctx); + return 1; + } + + /* 6. Добавляем данные окружения (АЦП) */ + AdcSample samples; + samples.channel1 = (uint16_t) (1); + samples.channel2 = (uint16_t) (2); + samples.channel3 = (uint16_t) (3); + samples.channel4 = (uint16_t) (4); + samples.channel5 = (uint16_t) (5); + + EnvDataStruct envData = { + .name = "ADC", + .data = (uint8_t *) &samples, + .data_len = sizeof(samples), + .timestamp = 9300 + }; + if (blf_add_env_data_struct(&ctx, &envData) != 0) { + BLF_ERROR_PRINTF("Failed to add ENV_DATA\n"); + blf_close(&ctx); + return 1; + } + + /* 7. Завершаем контейнер */ + if (blf_end_container(&ctx) != 0) { + BLF_ERROR_PRINTF("Failed to end container\n"); + blf_close(&ctx); + return 1; + } + + /* 8. Закрываем файл (заголовок обновляется автоматически) */ + ret = blf_close(&ctx); + if (ret != 0) { + BLF_ERROR_PRINTF("Failed to close file\n"); + return 1; + } + + BLF_ERROR_PRINTF("File log.blf created. Top-level objects: %d\n", ctx.objectCount); + return 0; +} \ No newline at end of file diff --git a/APP/modular.json b/APP/modular.json new file mode 100644 index 0000000..e097b40 --- /dev/null +++ b/APP/modular.json @@ -0,0 +1,12 @@ +{ + + "cmake": { + "inc_dirs": [ + "./" + ], + "srcs": [ + "./ert_main.c", + "./*.c" + ] + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9172da0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.17) +project(conv) + +set(CMAKE_C_STANDARD 17) + +include(modular.cmake) + +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_CFLAGS_DEBUG} -Os -std=c99 -D_GNU_SOURCE -lrt ") + +add_executable(conv ${SOURCES}) +target_link_libraries(conv PRIVATE m) + + diff --git a/modular.json b/modular.json new file mode 100644 index 0000000..6034ef1 --- /dev/null +++ b/modular.json @@ -0,0 +1,14 @@ +{ + "dep": [ + { + "type": "local", + "dir": "APP" + } + ], + "cmake": { + "inc_dirs": [ + ], + "srcs": [ + ] + } +} \ No newline at end of file