CSV file save on SPIFF filesystem, 10MB on internal flash

This commit is contained in:
Emanuele Trabattoni
2026-04-07 13:21:27 +02:00
parent f36cb96f21
commit 668b590d7c
11 changed files with 1032 additions and 27 deletions

View File

@@ -0,0 +1,60 @@
#include "datasave.h"
void save_history(const PSRAMVector<ignitionBoxStatus> &history)
{
// Initialize SPIFFS
if (!SPIFFS.begin(true))
{
LOG_ERROR("Failed to mount SPIFFS");
LOG_ERROR("5 seconds to restart...");
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
}
else
{
LOG_INFO("SPIFFS mounted successfully");
return;
}
std::ofstream ofs("/spiffs/ignA_history.csv", std::ios::out | std::ios::trunc);
if (ofs.fail())
{
LOG_ERROR("Failed to open file for writing");
return;
}
//write csv header
ofs << "TS,\
EVENTS_12,DLY_12,STAT_12,V_12_1,V_12_2,V_12_3,V_12_4,IGNITION_MODE_12,\
EVENTS_34,DLY_34,STAT_34,V_34_1,V_34_2,V_34_3,V_34_4,IGNITION_MODE_34,\
ENGINE_RPM,ADC_READTIME,N_QUEUE_ERRORS";
for (auto &entry : history)
{
ofs << std::to_string(entry.timestamp) << ","
<< std::to_string(entry.coils12.n_events) << ","
<< std::to_string(entry.coils12.spark_delay) << ","
<< std::string(sparkStatusNames.at(entry.coils12.spark_status)) << ","
<< std::to_string(entry.coils12.peak_p_in) << ","
<< std::to_string(entry.coils12.peak_n_in) << ","
<< std::to_string(entry.coils12.peak_p_out) << ","
<< std::to_string(entry.coils12.peak_n_out) << ","
<< std::string(softStartStatusNames.at(entry.coils12.sstart_status)) << ","
<< std::to_string(entry.coils34.n_events) << ","
<< std::to_string(entry.coils34.spark_delay) << ","
<< std::string(sparkStatusNames.at(entry.coils34.spark_status)) << ","
<< std::to_string(entry.coils34.peak_p_in) << ","
<< std::to_string(entry.coils34.peak_n_in) << ","
<< std::to_string(entry.coils34.peak_p_out) << ","
<< std::to_string(entry.coils34.peak_n_out) << ","
<< std::string(softStartStatusNames.at(entry.coils34.sstart_status)) << ","
<< std::to_string(entry.eng_rpm) << ","
<< std::to_string(entry.adc_read_time) << ","
<< std::to_string(entry.n_queue_errors);
ofs << std::endl;
}
auto written_bytes = ofs.tellp();
ofs.flush();
ofs.close();
LOG_INFO("Ignition A history saved to SPIFFS, bytes written: ", (uint32_t)written_bytes);
SPIFFS.end(); // unmount SPIFFS to ensure data is written and avoid corruption on next mount
}

View File

@@ -0,0 +1,24 @@
#pragma once
// System Includes
#include <Arduino.h>
#include <DebugLog.h>
#include <SPIFFS.h>
#include <string>
#include <fstream>
// Project Includes
#include "isr.h"
#include "psvector.h"
const bool SAVE_HISTORY_TO_SPIFFS = true; // Set to true to enable saving history to SPIFFS, false to disable
static void saveHistoryTask(void *pvParameters)
{
auto *history = static_cast<PSRAMVector<ignitionBoxStatus> *>(pvParameters);
save_history(*history);
vTaskDelete(NULL);
}
void save_history(const PSRAMVector<ignitionBoxStatus> &history);

View File

@@ -15,8 +15,8 @@
#define CORE_0 0
#define CORE_1 1
#define TASK_STACK 4096 // in words
#define TASK_PRIORITY (configMAX_PRIORITIES - 4) // highest priority after wifi tasks
#define RT_TASK_STACK 4096 // in words
#define RT_TASK_PRIORITY (configMAX_PRIORITIES - 4) // highest priority after wifi tasks
// =====================
// Event Flags (bitmask)

View File

@@ -9,6 +9,8 @@
// Definitions
#include <tasks.h>
#include <devices.h>
#include <psvector.h>
#include <datasave.h>
#include <ui.h>
// FreeRTOS directives
@@ -18,13 +20,6 @@
// #define CH_B_ENABLE
#define TEST
void printTaskStats()
{
char buffer[1024];
vTaskGetRunTimeStats(buffer);
Serial.println(buffer);
}
void setup()
{
Serial.begin(921600);
@@ -56,14 +51,22 @@ void loop()
{
// global variables
bool running = true;
static Devices dev;
const uint32_t max_history = 1024;
const uint32_t max_queue = 128;
PSRAMVector<ignitionBoxStatus> ignA_history_0(max_history);
PSRAMVector<ignitionBoxStatus> ignA_history_1(max_history);
auto &active_history = ignA_history_0;
auto &writable_history = ignA_history_1;
// Resources Initialization
static Devices dev;
// Task handle
static TaskHandle_t trigA_TaskHandle = NULL;
static TaskHandle_t trigB_TaskHandle = NULL;
static QueueHandle_t rt_taskA_queue = xQueueCreate(10, sizeof(ignitionBoxStatus));
static QueueHandle_t rt_taskB_queue = xQueueCreate(10, sizeof(ignitionBoxStatus));
// Data Queue for real time task to main loop communication
static QueueHandle_t rt_taskA_queue = xQueueCreate(max_queue, sizeof(ignitionBoxStatus));
static QueueHandle_t rt_taskB_queue = xQueueCreate(max_queue, sizeof(ignitionBoxStatus));
static rtTaskParams taskA_params{
.rt_running = true,
.dev = &dev,
@@ -82,7 +85,7 @@ void loop()
LOG_INFO("Task Variables OK");
#ifdef CH_B_ENABLE
QueueHandle_t rt_taskB_queue = xQueueCreate(10, sizeof(ignitionBoxStatus));
QueueHandle_t rt_taskB_queue = xQueueCreate(max_queue, sizeof(ignitionBoxStatus));
rtTaskParams taskB_params{
.rt_running = true,
.dev = &dev,
@@ -99,18 +102,19 @@ void loop()
.rt_resets = rtTaskResets{.rst_io_12p = RST_EXT_B12P, .rst_io_12n = RST_EXT_B12N, .rst_io_34p = RST_EXT_B34P, .rst_io_34n = RST_EXT_B34N}};
#endif
// Spi ok flags
bool spiA_ok = true;
bool spiB_ok = true;
// Init 2 SPI interfaces
SPIClass SPI_A(FSPI);
spiA_ok = SPI_A.begin(SPI_A_SCK, SPI_A_MISO, SPI_A_MOSI);
SPI_A.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1
#ifndef TEST
#ifndef TEST
SPIClass SPI_B(HSPI);
spiB_ok = SPI_B.begin(SPI_B_SCK, SPI_B_MISO, SPI_B_MOSI);
SPI_B.setDataMode(SPI_MODE1); // ADS1256 requires SPI mode 1
#endif
#endif
if (!spiA_ok || !spiB_ok)
{
LOG_ERROR("Unable to Initialize SPI Busses");
@@ -141,9 +145,9 @@ void loop()
ignA_task_success = xTaskCreatePinnedToCore(
rtIgnitionTask,
"rtIgnitionTask_boxA",
TASK_STACK,
RT_TASK_STACK,
(void *)&taskA_params,
TASK_PRIORITY,
RT_TASK_PRIORITY,
&trigA_TaskHandle,
CORE_0);
@@ -153,16 +157,16 @@ void loop()
ignB_task_success = xTaskCreatePinnedToCore(
rtIgnitionTask,
"rtIgnitionTask_boxB",
TASK_STACK,
RT_TASK_STACK,
(void *)&taskB_params,
TASK_PRIORITY, // priorità leggermente più alta
RT_TASK_PRIORITY, // priorità leggermente più alta
&trigB_TaskHandle,
CORE_1);
#endif
if ((ignA_task_success && ignB_task_success) != pdPASS)
{
LOG_ERROR("Unble to initialize ISR task");
LOG_ERROR("Una ble to initialize ISR task");
LOG_ERROR("5 seconds to restart...");
vTaskDelay(pdMS_TO_TICKS(5000));
esp_restart();
@@ -173,14 +177,28 @@ void loop()
////////////////////// MAIN LOOP //////////////////////
clearScreen();
setCursor(0, 0);
uint32_t counter = 0;
ignitionBoxStatus ignA;
int64_t last = esp_timer_get_time();
uint32_t missed_firings12 = 0;
uint32_t missed_firings34 = 0;
uint32_t counter = 0;
while (running)
{
if (counter == active_history.size())
{
counter = 0;
std::swap(active_history, writable_history); // switch active and writable buffers
if (SAVE_HISTORY_TO_SPIFFS)
xTaskCreate(
saveHistoryTask,
"saveHistoryTask",
RT_TASK_STACK / 2,
&writable_history,
RT_TASK_PRIORITY + 1, // higher priority to ensure it runs asap after buffer switch
NULL);
}
if (xQueueReceive(rt_taskA_queue, &ignA, pdMS_TO_TICKS(1000)) == pdTRUE)
{
if (ignA.coils12.spark_status == sparkStatus::SPARK_POS_FAIL || ignA.coils12.spark_status == sparkStatus::SPARK_NEG_FAIL)
@@ -218,6 +236,8 @@ void loop()
printField("Engine RPM", ignA.eng_rpm);
printField("ADC Read Time", ignA.adc_read_time);
printField("Queue Errors", ignA.n_queue_errors);
active_history[counter++ % active_history.size()] = ignA;
}
else
{

View File

@@ -0,0 +1,30 @@
#pragma once
#include <vector>
#include "esp_heap_caps.h"
// Allocator custom per PSRAM
template <typename T>
struct PSRAMAllocator {
using value_type = T;
PSRAMAllocator() noexcept {}
template <typename U>
PSRAMAllocator(const PSRAMAllocator<U>&) noexcept {}
T* allocate(std::size_t n) {
void* ptr = heap_caps_malloc(n * sizeof(T), MALLOC_CAP_SPIRAM);
if (!ptr) {
throw std::bad_alloc();
}
return static_cast<T*>(ptr);
}
void deallocate(T* p, std::size_t) noexcept {
heap_caps_free(p);
}
};
template <typename T>
using PSRAMVector = std::vector<T, PSRAMAllocator<T>>;

View File

@@ -4,3 +4,10 @@
#include <string>
std::string printBits(uint32_t value);
static void saveHistoryTask(void *pvParameters)
{
auto *history = static_cast<PSRAMVector<ignitionBoxStatus> *>(pvParameters);
save_history(*history);
vTaskDelete(NULL);
}