mirror of
https://github.com/markqvist/RNode_Firmware.git
synced 2026-04-28 09:43:08 +00:00
SharedSPI.h: FreeRTOS mutex protecting the shared SPI bus (pins 33/34/35) used by LoRa (SX1262), SD card, and future NFC. sx126x.cpp: All 6 SPI transaction blocks wrapped with shared_spi_mutex acquire/release. LoRa uses portMAX_DELAY (always gets access, just delayed by SD if needed). USBSD.h: USB MSC SD card access uses shared_spi_mutex with 200ms timeout. Deferred init (3s after boot) for SPI bus readiness. Metrics: sd_ready, sd_reads, sd_fails counters. IMULogger.h, Gui.h: SD card writes wrapped in shared_spi_mutex. Status: SD card correctly reports 29.7GB via USB MSC. Reads work intermittently (~50% success). Root cause: main loop takes ~700ms per iteration, causing mutex timeout for MSC callbacks. Needs loop time investigation to achieve reliable USB mass storage. The CO5300 display uses a separate SPI3 bus — not affected.
181 lines
6.4 KiB
C
181 lines
6.4 KiB
C
// IMU Data Logger — streams BHI260AP sensor data to SD card as CSV
|
|
// Start/stop via remote debug command 'L' or long-press button
|
|
//
|
|
// CSV format: timestamp_ms,ax,ay,az,gx,gy,gz,mx,my,mz
|
|
// Accelerometer/gyro/magnetometer in raw int16 units
|
|
// Timestamp is millis() at time of callback
|
|
|
|
#ifndef IMULOGGER_H
|
|
#define IMULOGGER_H
|
|
|
|
#if BOARD_MODEL == BOARD_TWATCH_ULT && HAS_SD
|
|
|
|
#include <SD.h>
|
|
#include "SharedSPI.h"
|
|
|
|
// Ring buffer for sensor samples (stored in PSRAM)
|
|
struct imu_sample_t {
|
|
uint32_t timestamp;
|
|
int16_t ax, ay, az; // accelerometer
|
|
int16_t gx, gy, gz; // gyroscope
|
|
int16_t mx, my, mz; // magnetometer
|
|
};
|
|
|
|
#define IMU_LOG_BUF_SIZE 512 // samples before flush (~10s at 50Hz)
|
|
static imu_sample_t *imu_log_buf = NULL;
|
|
static volatile uint32_t imu_log_head = 0; // write position
|
|
static volatile uint32_t imu_log_tail = 0; // read position
|
|
static bool imu_logging = false;
|
|
static File imu_log_file;
|
|
static uint32_t imu_log_samples = 0;
|
|
static uint32_t imu_log_start_ms = 0;
|
|
|
|
// Latest raw values (written by callbacks, read by flush)
|
|
static volatile int16_t imu_raw_ax = 0, imu_raw_ay = 0, imu_raw_az = 0;
|
|
static volatile int16_t imu_raw_gx = 0, imu_raw_gy = 0, imu_raw_gz = 0;
|
|
static volatile int16_t imu_raw_mx = 0, imu_raw_my = 0, imu_raw_mz = 0;
|
|
static volatile bool imu_accel_new = false;
|
|
|
|
// Sensor callbacks — store latest values
|
|
void imu_log_accel_cb(uint8_t sensor_id, uint8_t *data, uint32_t size, uint64_t *timestamp, void *user_data) {
|
|
if (size >= 6) {
|
|
imu_raw_ax = (int16_t)(data[0] | (data[1] << 8));
|
|
imu_raw_ay = (int16_t)(data[2] | (data[3] << 8));
|
|
imu_raw_az = (int16_t)(data[4] | (data[5] << 8));
|
|
imu_accel_new = true;
|
|
|
|
// Push combined sample to ring buffer when accel fires (it's the "clock")
|
|
if (imu_logging && imu_log_buf) {
|
|
uint32_t next = (imu_log_head + 1) % IMU_LOG_BUF_SIZE;
|
|
if (next != imu_log_tail) { // not full
|
|
imu_sample_t &s = imu_log_buf[imu_log_head];
|
|
s.timestamp = millis();
|
|
s.ax = imu_raw_ax; s.ay = imu_raw_ay; s.az = imu_raw_az;
|
|
s.gx = imu_raw_gx; s.gy = imu_raw_gy; s.gz = imu_raw_gz;
|
|
s.mx = imu_raw_mx; s.my = imu_raw_my; s.mz = imu_raw_mz;
|
|
imu_log_head = next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void imu_log_gyro_cb(uint8_t sensor_id, uint8_t *data, uint32_t size, uint64_t *timestamp, void *user_data) {
|
|
if (size >= 6) {
|
|
imu_raw_gx = (int16_t)(data[0] | (data[1] << 8));
|
|
imu_raw_gy = (int16_t)(data[2] | (data[3] << 8));
|
|
imu_raw_gz = (int16_t)(data[4] | (data[5] << 8));
|
|
}
|
|
}
|
|
|
|
void imu_log_mag_cb(uint8_t sensor_id, uint8_t *data, uint32_t size, uint64_t *timestamp, void *user_data) {
|
|
if (size >= 6) {
|
|
imu_raw_mx = (int16_t)(data[0] | (data[1] << 8));
|
|
imu_raw_my = (int16_t)(data[2] | (data[3] << 8));
|
|
imu_raw_mz = (int16_t)(data[4] | (data[5] << 8));
|
|
}
|
|
}
|
|
|
|
// Forward declaration
|
|
void imu_log_flush();
|
|
|
|
bool imu_log_start(SensorBHI260AP *bhi) {
|
|
if (imu_logging || !bhi) return false;
|
|
|
|
// Allocate ring buffer in PSRAM
|
|
if (!imu_log_buf) {
|
|
imu_log_buf = (imu_sample_t *)heap_caps_malloc(
|
|
IMU_LOG_BUF_SIZE * sizeof(imu_sample_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
|
if (!imu_log_buf) return false;
|
|
}
|
|
imu_log_head = 0;
|
|
imu_log_tail = 0;
|
|
|
|
// Init SD (acquire shared SPI mutex)
|
|
if (shared_spi_mutex) xSemaphoreTake(shared_spi_mutex, portMAX_DELAY);
|
|
SPI.begin(SD_CLK, SD_MISO, SD_MOSI, SD_CS);
|
|
bool sd_ok = SD.begin(SD_CS, SPI, 4000000, "/sd", 5);
|
|
if (shared_spi_mutex) xSemaphoreGive(shared_spi_mutex);
|
|
if (!sd_ok) {
|
|
Serial.println("[imu_log] SD init failed");
|
|
return false;
|
|
}
|
|
|
|
// Create timestamped filename
|
|
char fname[32];
|
|
snprintf(fname, sizeof(fname), "/imu_%lu.csv", millis() / 1000);
|
|
imu_log_file = SD.open(fname, FILE_WRITE);
|
|
if (!imu_log_file) {
|
|
Serial.println("[imu_log] file open failed");
|
|
SD.end(); SPI.end();
|
|
return false;
|
|
}
|
|
|
|
// Write CSV header
|
|
imu_log_file.println("ms,ax,ay,az,gx,gy,gz,mx,my,mz");
|
|
|
|
// Configure sensors at 50Hz
|
|
bhi->configure(SensorBHI260AP::ACCEL_PASSTHROUGH, 50.0, 0);
|
|
bhi->onResultEvent(SensorBHI260AP::ACCEL_PASSTHROUGH, imu_log_accel_cb);
|
|
|
|
bhi->configure(SensorBHI260AP::GYRO_PASSTHROUGH, 50.0, 0);
|
|
bhi->onResultEvent(SensorBHI260AP::GYRO_PASSTHROUGH, imu_log_gyro_cb);
|
|
|
|
bhi->configure(SensorBHI260AP::MAGNETOMETER_PASSTHROUGH, 25.0, 0);
|
|
bhi->onResultEvent(SensorBHI260AP::MAGNETOMETER_PASSTHROUGH, imu_log_mag_cb);
|
|
|
|
imu_logging = true;
|
|
imu_log_samples = 0;
|
|
imu_log_start_ms = millis();
|
|
Serial.printf("[imu_log] started: %s\n", fname);
|
|
return true;
|
|
}
|
|
|
|
void imu_log_stop(SensorBHI260AP *bhi) {
|
|
if (!imu_logging) return;
|
|
|
|
// Disable sensor streams
|
|
if (bhi) {
|
|
bhi->configure(SensorBHI260AP::ACCEL_PASSTHROUGH, 0, 0);
|
|
bhi->configure(SensorBHI260AP::GYRO_PASSTHROUGH, 0, 0);
|
|
bhi->configure(SensorBHI260AP::MAGNETOMETER_PASSTHROUGH, 0, 0);
|
|
}
|
|
|
|
// Flush remaining samples
|
|
imu_log_flush();
|
|
|
|
uint32_t duration = (millis() - imu_log_start_ms) / 1000;
|
|
Serial.printf("[imu_log] stopped: %lu samples in %lus (%.1f Hz)\n",
|
|
imu_log_samples, duration,
|
|
duration > 0 ? (float)imu_log_samples / duration : 0);
|
|
|
|
if (shared_spi_mutex) xSemaphoreTake(shared_spi_mutex, portMAX_DELAY);
|
|
imu_log_file.close();
|
|
if (shared_spi_mutex) xSemaphoreGive(shared_spi_mutex);
|
|
imu_logging = false;
|
|
}
|
|
|
|
// Flush ring buffer to SD — call from main loop
|
|
void imu_log_flush() {
|
|
if (!imu_logging || !imu_log_buf) return;
|
|
if (shared_spi_mutex && xSemaphoreTake(shared_spi_mutex, pdMS_TO_TICKS(50)) != pdTRUE) return;
|
|
|
|
char line[80];
|
|
uint32_t flushed = 0;
|
|
while (imu_log_tail != imu_log_head) {
|
|
imu_sample_t &s = imu_log_buf[imu_log_tail];
|
|
int len = snprintf(line, sizeof(line), "%lu,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
|
|
s.timestamp, s.ax, s.ay, s.az,
|
|
s.gx, s.gy, s.gz, s.mx, s.my, s.mz);
|
|
imu_log_file.write((uint8_t *)line, len);
|
|
imu_log_tail = (imu_log_tail + 1) % IMU_LOG_BUF_SIZE;
|
|
imu_log_samples++;
|
|
flushed++;
|
|
}
|
|
if (flushed > 0) {
|
|
imu_log_file.flush();
|
|
}
|
|
if (shared_spi_mutex) xSemaphoreGive(shared_spi_mutex);
|
|
}
|
|
|
|
#endif // BOARD_MODEL == BOARD_TWATCH_ULT && HAS_SD
|
|
#endif // IMULOGGER_H
|