diff --git a/firmware/Makefile b/firmware/Makefile index c006b30b6e..8a79e9cdbd 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -297,6 +297,7 @@ INCDIR = $(CHIBIOS)/os/license \ console_util \ console \ $(PROJECT_DIR)/console/binary \ + $(PROJECT_DIR)/console/binary_log \ $(PROJECT_DIR)/console/fl_binary \ $(PROJECT_DIR)/hw_layer \ $(PROJECT_DIR)/mass_storage \ diff --git a/firmware/bootloader/src/Makefile b/firmware/bootloader/src/Makefile index 0144b7bc53..d465949857 100644 --- a/firmware/bootloader/src/Makefile +++ b/firmware/bootloader/src/Makefile @@ -242,6 +242,7 @@ INCDIR = .. $(CHIBIOS)/os/license \ $(PROJECT_DIR)/console_util \ $(PROJECT_DIR)/console \ $(PROJECT_DIR)/console/binary \ + $(PROJECT_DIR)/console/binary_log \ $(PROJECT_DIR)/console/fl_binary \ $(PROJECT_DIR)/hw_layer \ $(PROJECT_DIR)/mass_storage \ diff --git a/firmware/console/binary_log/binary_logging.cpp b/firmware/console/binary_log/binary_logging.cpp new file mode 100644 index 0000000000..066ebf6611 --- /dev/null +++ b/firmware/console/binary_log/binary_logging.cpp @@ -0,0 +1,123 @@ + +#include "tunerstudio_configuration.h" +#include "log_field.h" +#include "efilib.h" +#include "efitime.h" +#include "crc.h" + +static const LogField fields[] = { + {tsOutputChannels.rpm, "RPM", "rpm", 0}, + {tsOutputChannels.vehicleSpeedKph, GAUGE_NAME_VVS, "kph", 0}, + {tsOutputChannels.internalMcuTemperature, GAUGE_NAME_CPU_TEMP, "C", 0}, + {tsOutputChannels.coolantTemperature, "CLT", "C", 1}, + {tsOutputChannels.intakeAirTemperature, "IAT", "C", 1}, + {tsOutputChannels.throttlePosition, "TPS", "%", 2}, + {tsOutputChannels.pedalPosition, GAUGE_NAME_THROTTLE_PEDAL, "%", 2}, + {tsOutputChannels.manifoldAirPressure, "MAP", "kPa", 1}, + {tsOutputChannels.airFuelRatio, GAUGE_NAME_AFR, "afr", 2}, + {tsOutputChannels.vBatt, GAUGE_NAME_VBAT, "v", 2}, + {tsOutputChannels.oilPressure, "Oil Press", "kPa", 0}, + {tsOutputChannels.vvtPosition, GAUGE_NAME_VVT, "deg", 1}, + {tsOutputChannels.chargeAirMass, GAUGE_NAME_AIR_MASS, "g", 3}, + {tsOutputChannels.currentTargetAfr, GAUGE_NAME_TARGET_AFR, "afr", 2}, + {tsOutputChannels.fuelBase, GAUGE_NAME_FUEL_BASE, "ms", 3}, + {tsOutputChannels.fuelRunning, GAUGE_NAME_FUEL_RUNNING, "ms", 3}, + {tsOutputChannels.actualLastInjection, GAUGE_NAME_FUEL_LAST_INJECTION, "ms", 3}, + {tsOutputChannels.injectorDutyCycle, GAUGE_NAME_FUEL_INJ_DUTY, "%", 0}, + {tsOutputChannels.veValue, GAUGE_NAME_FUEL_VE, "%", 1}, + {tsOutputChannels.tCharge, "tCharge", "C", 1}, + {tsOutputChannels.injectorLagMs, GAUGE_NAME_INJECTOR_LAG, "ms", 3}, + {tsOutputChannels.fuelPidCorrection, GAUGE_NAME_FUEL_PID_CORR, "ms", 3}, + {tsOutputChannels.wallFuelCorrection, GAUGE_NAME_FUEL_WALL_CORRECTION, "ms", 3}, + {tsOutputChannels.tpsAccelFuel, GAUGE_NAME_FUEL_TPS_EXTRA, "ms", 3}, + {tsOutputChannels.ignitionAdvance, GAUGE_NAME_TIMING_ADVANCE, "deg", 1}, + {tsOutputChannels.sparkDwell, GAUGE_COIL_DWELL_TIME, "ms", 1}, + {tsOutputChannels.coilDutyCycle, GAUGE_NAME_DWELL_DUTY, "%", 0}, + {tsOutputChannels.idlePosition, GAUGE_NAME_IAC, "%", 1}, + {tsOutputChannels.etbTarget, "ETB Target", "%", 2}, + {tsOutputChannels.etb1DutyCycle, "ETB Duty", "%", 1}, + {tsOutputChannels.etb1Error, "ETB Error", "%", 3}, + {tsOutputChannels.fuelTankLevel, "fuel level", "%", 0}, +}; + +void writeHeader(char* buffer) { + // File format: MLVLG\0 + strncpy(buffer, "MLVLG", 6); + + // Format version = 01 + buffer[6] = 0; + buffer[7] = 1; + + // Timestamp + buffer[8] = 0; + buffer[9] = 0; + buffer[10] = 0; + buffer[11] = 0; + + // Info data start + buffer[12] = 0; + buffer[13] = 0; + + // Data begin index - always begin at 4096 = 0x800 bytes to allow space for header + buffer[14] = 0; + buffer[15] = 0; + buffer[16] = 0x08; + buffer[17] = 0x00; + + // Record length - length of a single data record: sum size of all fields + uint16_t recLength = 0; + for (size_t i = 0; i < efi::size(fields); i++) { + recLength += fields[i].getSize(); + } + + buffer[18] = recLength >> 8; + buffer[19] = recLength & 0xFF; + + // Number of logger fields + buffer[20] = 0; + buffer[21] = efi::size(fields); + + // Write the actual logger fields, offset 22 + char* entryHeaders = buffer + 22; + for (size_t i = 0; i < efi::size(fields); i++) { + size_t sz = fields[i].writeHeader(entryHeaders); + entryHeaders += sz; + } +} + +static uint8_t blockRollCounter = 0; + +size_t writeBlock(char* buffer) { + // Offset 0 = Block type, standard data block in this case + buffer[0] = 0; + + // Offset 1 = rolling counter sequence number + buffer[1] = blockRollCounter++; + + // Offset 2, size 2 = Timestamp at 10us resolution + uint16_t timestamp = getTimeNowUs() / 10; + buffer[2] = timestamp >> 8; + buffer[3] = timestamp & 0xFF; + + // Offset 4 = field data + const char* dataBlockStart = buffer + 4; + char* dataBlock = buffer + 4; + for (size_t i = 0; i < efi::size(fields); i++) { + size_t entrySize = fields[i].writeData(dataBlock); + + // Increment pointer to next entry + dataBlock += entrySize; + } + + size_t dataBlockSize = dataBlock - dataBlockStart; + + // "CRC" at the end is just the sum of all bytes + uint8_t sum = 0; + for (size_t i = 0; i < dataBlockSize; i++) { + sum += dataBlockStart[i]; + } + *dataBlock = sum; + + // Total size has 4 byte header + 1 byte checksum + return dataBlockSize + 5; +} diff --git a/firmware/console/binary_log/binary_logging.h b/firmware/console/binary_log/binary_logging.h new file mode 100644 index 0000000000..0234698024 --- /dev/null +++ b/firmware/console/binary_log/binary_logging.h @@ -0,0 +1,4 @@ +#include + +void writeHeader(char* buffer); +size_t writeBlock(char* buffer); diff --git a/firmware/console/binary_log/log_field.cpp b/firmware/console/binary_log/log_field.cpp new file mode 100644 index 0000000000..6b0d8eb7cc --- /dev/null +++ b/firmware/console/binary_log/log_field.cpp @@ -0,0 +1,52 @@ +#include "log_field.h" + +#include + +static void memcpy_swapend(void* dest, const void* src, size_t num) { + const char* src2 = reinterpret_cast(src); + char* dest2 = reinterpret_cast(dest); + + for (size_t i = 0; i < num; i++) { + // Endian swap - copy the end to the beginning + dest2[i] = src2[num - 1 - i]; + } +} + +static void copyFloat(char* buffer, float value) { + memcpy_swapend(buffer, reinterpret_cast(&value), sizeof(float)); +} + +size_t LogField::writeHeader(char* buffer) const { + // Offset 0, length 1 = type + buffer[0] = static_cast(m_type); + + // Offset 0, length 34 = name + strncpy(&buffer[1], m_name, 34); + + // Offset 35, length 10 = units + strncpy(&buffer[35], m_units, 10); + + // Offset 45, length 1 = Display style + // value 0 -> floating point number + buffer[45] = 0; + + // Offset 46, length 4 = Scale + copyFloat(buffer + 46, 1.0f / m_multiplier); + + // Offset 50, length 4 = shift before scaling (always 0) + copyFloat(buffer + 50, 0); + + // Offset 54, size 1 = digits to display (signed int) + buffer[54] = m_digits; + + // Total size = 55 + return 55; +} + +size_t LogField::writeData(char* buffer) const { + size_t size = m_size; + + memcpy_swapend(buffer, m_addr, size); + + return size; +} diff --git a/firmware/console/binary_log/log_field.h b/firmware/console/binary_log/log_field.h new file mode 100644 index 0000000000..0ab289fa9f --- /dev/null +++ b/firmware/console/binary_log/log_field.h @@ -0,0 +1,105 @@ +#pragma once + +#include "scaled_channel.h" +#include +#include + +class LogField { +public: + template + LogField(const scaled_channel& toRead, const char* name, const char* units, int8_t digits) + : m_type(resolveType()) + , m_multiplier(TMult) + , m_addr(reinterpret_cast(&toRead)) + , m_digits(digits) + , m_size(sizeForType(resolveType())) + , m_name(name) + , m_units(units) + { + } + + enum class Type : uint8_t { + U08 = 0, + S08 = 1, + U16 = 2, + S16 = 3, + U32 = 4, + S32 = 5, + S64 = 6, + F32 = 7, + }; + + size_t getSize() const { + return m_size; + } + + // Write the header data describing this field. + // Returns the number of bytes written. + size_t writeHeader(char* buffer) const; + + // Write the field's data to the buffer. + // Returns the number of bytes written. + size_t writeData(char* buffer) const; + +private: + template + static Type resolveType(); + + static size_t sizeForType(Type t) { + switch (t) { + case Type::U08: + case Type::S08: + return 1; + case Type::U16: + case Type::S16: + return 2; + default: + // float, uint32, int32 + return 4; + } + } + + const Type m_type; + const float m_multiplier; + const char* const m_addr; + const int8_t m_digits; + const size_t m_size; + + const char* const m_name; + const char* const m_units; +}; + +template<> +constexpr LogField::Type LogField::resolveType() { + return Type::U08; +} + +template<> +constexpr LogField::Type LogField::resolveType() { + return Type::S08; +} + +template<> +constexpr LogField::Type LogField::resolveType() { + return Type::U16; +} + +template<> +constexpr LogField::Type LogField::resolveType() { + return Type::S16; +} + +template<> +constexpr LogField::Type LogField::resolveType() { + return Type::U32; +} + +template<> +constexpr LogField::Type LogField::resolveType() { + return Type::S32; +} + +template<> +constexpr LogField::Type LogField::resolveType() { + return Type::F32; +} diff --git a/firmware/console/console.mk b/firmware/console/console.mk index d7344a5a28..b8c0c57f6b 100644 --- a/firmware/console/console.mk +++ b/firmware/console/console.mk @@ -4,5 +4,7 @@ CONSOLESRC = CONSOLE_SRC_CPP = $(PROJECT_DIR)/console/status_loop.cpp \ $(PROJECT_DIR)/console/console_io.cpp \ $(PROJECT_DIR)/console/eficonsole.cpp \ - $(PROJECT_DIR)/console/tooth_logger.cpp + $(PROJECT_DIR)/console/tooth_logger.cpp \ + $(PROJECT_DIR)/console/binary_log/log_field.cpp \ + $(PROJECT_DIR)/console/binary_log/binary_logging.cpp \ diff --git a/firmware/controllers/engine_controller.cpp b/firmware/controllers/engine_controller.cpp index ef9aa53554..f8bd508b2e 100644 --- a/firmware/controllers/engine_controller.cpp +++ b/firmware/controllers/engine_controller.cpp @@ -696,7 +696,7 @@ void initEngineContoller(Logging *sharedLogger DECLARE_ENGINE_PARAMETER_SUFFIX) * UNUSED_SIZE contants. */ #ifndef RAM_UNUSED_SIZE -#define RAM_UNUSED_SIZE 10900 +#define RAM_UNUSED_SIZE 10000 #endif #ifndef CCM_UNUSED_SIZE #define CCM_UNUSED_SIZE 2900 diff --git a/simulator/Makefile b/simulator/Makefile index 42041676f4..7ce9ae89cf 100644 --- a/simulator/Makefile +++ b/simulator/Makefile @@ -190,6 +190,7 @@ INCDIR = . \ $(PROJECT_DIR)/init \ $(PROJECT_DIR)/console \ $(PROJECT_DIR)/console/binary \ + $(PROJECT_DIR)/console/binary_log \ $(PROJECT_DIR)/console/fl_binary \ $(PROJECT_DIR)/config/engines \ $(PROJECT_DIR)/ext_algo \ diff --git a/unit_tests/Makefile b/unit_tests/Makefile index 0dc0d51827..ad73b6e91b 100644 --- a/unit_tests/Makefile +++ b/unit_tests/Makefile @@ -141,7 +141,8 @@ CPPSRC = $(UTILSRC_CPP) \ $(HW_SENSORS_SRC) \ $(TRIGGER_SRC_CPP) \ $(INIT_SRC_CPP) \ - $(PROJECT_DIR)/../unit_tests/main.cpp + $(PROJECT_DIR)/../unit_tests/main.cpp \ + $(PROJECT_DIR)/console/binary_log/log_field.cpp \ # C sources to be compiled in ARM mode regardless of the global setting. @@ -173,6 +174,7 @@ INCDIR = . \ $(PROJECT_DIR)/config/engines \ $(CONTROLLERS_INC) \ $(PROJECT_DIR)/console \ + $(PROJECT_DIR)/console/binary_log \ $(DEVELOPMENT_DIR) \ $(PROJECT_DIR)/ext_algo \ $(PROJECT_DIR)/hw_layer \ diff --git a/unit_tests/tests/test_binary_log.cpp b/unit_tests/tests/test_binary_log.cpp new file mode 100644 index 0000000000..7a42f10f3b --- /dev/null +++ b/unit_tests/tests/test_binary_log.cpp @@ -0,0 +1,50 @@ +#include "log_field.h" + +#include + +using ::testing::ElementsAre; + +TEST(BinaryLogField, FieldHeader) { + scaled_channel channel; + LogField field(channel, "name", "units", 2); + + char buffer[56]; + memset(buffer, 0xAA, sizeof(buffer)); + + // Should write 55 bytes + EXPECT_EQ(55, field.writeHeader(buffer)); + + // Expect correctly written header + EXPECT_THAT(buffer, ElementsAre( + 1, // type: int8_t + // name - 34 bytes, 0 padded + 'n', 'a', 'm', 'e', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // units - 10 bytes, 0 padded + 'u', 'n', 'i', 't', 's', 0, 0, 0, 0, 0, + // display style: float + 0, + // Scale = 0.1 (float) + 0x3d, 0xcc, 0xcc, 0xcd, + // Transform - we always use 0 + 0, 0, 0, 0, + // Digits - 2, as configured + 2, + + // After end should be 0xAA, not touched + 0xAA + )); +} + +TEST(BinaryLogField, Value) { + scaled_channel testValue = 12345678; + LogField lf(testValue, "test", "unit", 0); + + char buffer[6]; + memset(buffer, 0xAA, sizeof(buffer)); + + // Should write 4 bytes + EXPECT_EQ(4, lf.writeData(buffer)); + + // Check that big endian data was written, and bytes after weren't touched + EXPECT_THAT(buffer, ElementsAre(0x00, 0xbc, 0x61, 0x4e, 0xAA, 0xAA)); +} diff --git a/unit_tests/tests/tests.mk b/unit_tests/tests/tests.mk index e4f0be8697..28166ef8ec 100644 --- a/unit_tests/tests/tests.mk +++ b/unit_tests/tests/tests.mk @@ -55,4 +55,5 @@ TESTS_SRC_CPP = \ tests/test_boost.cpp \ tests/test_gppwm.cpp \ tests/test_fuel_math.cpp \ + tests/test_binary_log.cpp \