295abb37ee
Firmware for an autonomous wave-measurement buoy (ATmega2560-based WII5 v2 board). Reads wave motion from a Sparton AHRS-M1/M2 IMU, samples GPS and battery state, and reports back over Iridium SBD satellite telemetry. Originally developed 2012-2024. This is the first public release. Code, documentation, and field-tested operating modes (Capture, Sleep, Position, ManualTest, SelfTest, LowBattery) are licensed under Apache 2.0 — see LICENSE and NOTICE. See README.md for an overview and build instructions, CONTRIBUTING.md for how to contribute, and DEPLOYMENTS.md for the field-deployment log.
1077 lines
33 KiB
C++
1077 lines
33 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> and WII5 Buoy contributors
|
|
//
|
|
// This file is part of WII5 Buoy firmware.
|
|
// See LICENSE for full terms.
|
|
|
|
/**
|
|
* @file WII5Sparton.cpp
|
|
* @brief Sparton AHRS-M1/M2 IMU driver: capture timing, NorthTek configuration.
|
|
*/
|
|
|
|
/*
|
|
|
|
WII5Sparton
|
|
|
|
TODO 2024 - Review code, check TODO and add some Command functions for testing
|
|
TODO 2024 - Look at getting debug working with serial passthrough in text stage not binary
|
|
TODO 2024 - When getting no data - stop capture and report failure for Iridium
|
|
TODO 2024 - How to send an error - look at Command return string
|
|
TODO 2024 - Debugging text modes?
|
|
|
|
BINARY File
|
|
https://forum.pjrc.com/threads/55114-SD-Datalogging-Best-Practice-in-2019
|
|
Datalogging is a problem I've been trying to brute-force for half a year now.
|
|
|
|
My solution (that I hope to release in a couple more months of testing) runs on top of the SdFs library for the purpose of low-latency high-frequency binary logging. I'm currently able to (somewhat reliably) maintain a logging speed of 925bytes at 500Hz (452KB/s). So in short, it is possible to have a good logging system, although it's highly dependent on how you interact with your data.
|
|
|
|
My current best practises:
|
|
|
|
1. Initialize the SdFs on SdioConfig(FIFO_SDIO) mode.
|
|
2. Preallocate the maximum length of your log file using the .preAllocate(size_t size) method.
|
|
3. Important: Pre-erase the entire file with zeroes.
|
|
4. Before you start logging (to ensure the file is committed fully to the card), call sync().
|
|
5. Write in chunks of a multiple of 512bytes only. (512, 1024, 2048 byte chucks per write() command)
|
|
6. To minimize blocking, try to write only if .card()->isBusy() returns false. This seems to be related to the internal card data management (I'm guessing things like TRIM and what not). Step 3 helps ensure that the card spends as little time as possible in this busy time.
|
|
7. Use a SLC-type SD card. I cannot stress enough the difference this makes. Before buying this card, I had write() operations take up to 10ms on a SanDisk card, but with this expensive card they 99.9% of the time stayed below 500us.
|
|
8. Call truncate() and then sync() after you've finished writing your entire file. The first function removes any extra pre-allocation and the second makes sure that it's a proper FAT32 file. Note that if you're using an exFAT partition your file will not be saved until you call sync() or close().
|
|
7. On that note, FAT32 is always faster than exFAT, but you do have less capacity.
|
|
|
|
I'm still learning more about best practises for logging. My current hang-ups are (I think) related to heap-overflow because of the many sensors I have attached interrupting one another during a write() operation. I'm a very fresh programmer so it's taking me a while but I hope the tips above are helpful enough for you to get started on your 100Hz project.
|
|
|
|
Good luck! Let me know if you find any new practises that I can also test.
|
|
|
|
|
|
64Hz Performance Issue:
|
|
|
|
1 - Version of code that uses standard library loge to disk. No parsing at all.
|
|
This will not allow us to show the 1 Hz update.
|
|
|
|
Status: On Hold
|
|
|
|
2 - Version of the code that uses new library to buffer and then parse int the loops
|
|
|
|
Status: WIP
|
|
|
|
3 - Binary Version: Most likely in interrupt
|
|
|
|
TODO - Cycle through all the baud rates until we get an OK/Huh so we can
|
|
work out it is wokring. Send the stop command as the test. Clear wii5BufferString.
|
|
Send twice, that sort of thing. If not our correct baud rate, then
|
|
send the program command change to new baud.
|
|
|
|
PINS:
|
|
|
|
* 1 - Ground
|
|
* 2 - TX (actually TX from AVR, so Receive)
|
|
* 3 - RX
|
|
* 4 - Power (+4 volts)
|
|
* 5 - NC
|
|
* 6 - RESET (low is reset, keep high, has weak pullup)
|
|
|
|
Special Notes:
|
|
|
|
* 5 millisecond line delay
|
|
*
|
|
|
|
baud 4 set drop<cr>
|
|
|
|
4 = 9600
|
|
5 = 19200
|
|
6 = 38400
|
|
7 = 57600
|
|
8 = 115200
|
|
|
|
Text CRC
|
|
|
|
TODO - We should at least check we have not lost characters
|
|
|
|
Binary.
|
|
|
|
Try RFS (Remote Function Select) Sparton Binary format with CRC etc
|
|
|
|
STATS:Sparton:loops=128008 ERROR:time=512 accel=96
|
|
Standard wii5BufferStrings
|
|
64Hz
|
|
|
|
# INFO: 2024-03-13T23:49:09 - SPARTON: Records received = 1001
|
|
STATS:Sparton:Status=unknown:age=165s
|
|
STATS:Sparton:Time:Total=0
|
|
STATS:Sparton:SerialManager:TODO
|
|
STATS:Sparton:loops=587117 ERROR:time=119 accel=3
|
|
8Hz, SD Card etc
|
|
|
|
# INFO: 2024-03-13T23:50:32 - SPARTON: Records received = 1001
|
|
STATS:Sparton:Status=unknown:age=248s
|
|
STATS:Sparton:Time:Total=0
|
|
STATS:Sparton:SerialManager:TODO
|
|
STATS:Sparton:loops=44851 ERROR:time=199 accel=32
|
|
64Hz, SD Card etc
|
|
|
|
*/
|
|
|
|
#include <Arduino.h>
|
|
#include <WII5Sparton.h>
|
|
|
|
// Careful - loss of serial data with this on
|
|
// #ifdef SPARTON_SERIAL_DEBUG
|
|
|
|
// #define WII5_ZONLY
|
|
// #define WII5_STRINGUTIL
|
|
// #ifdef WII5_STRINGUTIL
|
|
// #include <StringUtil.h>
|
|
// using namespace StringUtil;
|
|
// #endif
|
|
|
|
// INTERRUPT - Keep fast and use volatile varialbes.
|
|
void WII5Sparton::handleRxChar( uint8_t c ) {
|
|
// Which record we playing with?
|
|
currentRecord = (recordCount % SPARTON_RECORDS);
|
|
|
|
// \r and \n for records is an issue...
|
|
|
|
// START of Binary
|
|
if (c == 0x1) {
|
|
processMode = 2;
|
|
currentCount = 0;
|
|
startWhen = 0;
|
|
binEscape = false;
|
|
return;
|
|
}
|
|
// END of binary
|
|
else if (c == 0x3) {
|
|
// Record too small !
|
|
if (currentCount < sizeof(WII5_DATA_SpartonBinary)) {
|
|
serialSizeError++;
|
|
}
|
|
|
|
// Other validity
|
|
if (
|
|
(binRecords[currentRecord].channel != 0) // 0 - yep - valid
|
|
|| (binRecords[currentRecord].status != 105) // TODO 105, what is thta?
|
|
) {
|
|
// TODO Not really time, record data?
|
|
statsTimeError++;
|
|
}
|
|
|
|
// Status / Mode
|
|
lastValidRecord = currentRecord;
|
|
recordCount++;
|
|
processMode = 0;
|
|
|
|
// TODO consider logging time here for startWhen
|
|
return;
|
|
}
|
|
|
|
// Escape character - take off BIN next time
|
|
else if (c == 0x10) {
|
|
binEscape = true;
|
|
}
|
|
|
|
// Process a Binary row
|
|
else if (processMode == 2) {
|
|
if (binEscape) {
|
|
// Escape characters need to drop MSB ! (to protect 0x1, 0x3 and 0x10)
|
|
binEscape = false;
|
|
c = c & B01111111;
|
|
}
|
|
if (currentCount < sizeof(WII5_DATA_SpartonBinary)) {
|
|
((char*)&binRecords[currentRecord])[currentCount] = c;
|
|
currentCount++;
|
|
}
|
|
|
|
// Record too large
|
|
else {
|
|
serialSizeError++;
|
|
}
|
|
}
|
|
/*
|
|
TODO 2024 - passthrough mode?
|
|
else if (processMode == 0) {
|
|
console.log(LOG_DEBUG, F("SPARTON: char=%c"), c);
|
|
}
|
|
*/
|
|
}
|
|
|
|
void WII5Sparton::begin() {
|
|
// TODO Use ?
|
|
pinMode(SPARTON_RESET, OUTPUT);
|
|
digitalWrite(SPARTON_RESET, HIGH);
|
|
|
|
// Power (V2)
|
|
#if POWER_IMU_SPARTON_PIN>0
|
|
powerSetPin(POWER_IMU_SPARTON_PIN, POWER_IMU_SPARTON_ON);
|
|
powerOff(true); // Forced off at boot
|
|
#endif
|
|
|
|
debug = false;
|
|
running = false;
|
|
cancel = false;
|
|
step = WII5SPARTON_OFF;
|
|
|
|
consoleDirect = false;
|
|
rawCapture = false; // TODO NOTE: Debugging - probs should make this false by default
|
|
|
|
// Defaults
|
|
HZ = 8;
|
|
binMode = true;
|
|
|
|
// TODO - what should these be?
|
|
// recordCalc.setRejectionSigma(7.0);
|
|
|
|
};
|
|
|
|
uint16_t WII5Sparton::getHz() {
|
|
return HZ;
|
|
}
|
|
uint16_t WII5Sparton::getMs() {
|
|
return (1000 / getHz());
|
|
}
|
|
void WII5Sparton::setHz(uint16_t h) {
|
|
HZ = h;
|
|
}
|
|
void WII5Sparton::setBinary(bool in) {
|
|
binMode = in;
|
|
}
|
|
|
|
void WII5Sparton::setPassthrough(bool in) {} // {pt = in;}
|
|
bool WII5Sparton::getPassthrough() {} // {return pt;}
|
|
void WII5Sparton::setDebug(bool in) {debug = in;}
|
|
bool WII5Sparton::getDebug() {return debug;}
|
|
|
|
void WII5Sparton::setCapture(bool in) {
|
|
// TODO Capture disabled
|
|
capture = false; return;
|
|
// closeFile();
|
|
capture = in;
|
|
}
|
|
|
|
bool WII5Sparton::getCapture() {return capture;}
|
|
void WII5Sparton::setRecords(uint32_t in) { recordTotal = in; }
|
|
uint32_t WII5Sparton::getRecords() {return recordTotal;}
|
|
|
|
void WII5Sparton::automatic(uint8_t hz, uint32_t records, bool capture) {
|
|
setHz(hz);
|
|
setRecords(records);
|
|
// (leave as is) setPassthrough(0);
|
|
// Do not change debug
|
|
setCapture(capture);
|
|
start();
|
|
}
|
|
|
|
void WII5Sparton::start() {
|
|
console.log(LOG_DEBUG, F("SPARTON: start current=%d"), step);
|
|
|
|
pinMode(SPARTON_RESET, OUTPUT);
|
|
digitalWrite(SPARTON_RESET, HIGH);
|
|
|
|
if (step != WII5SPARTON_OFF) {
|
|
console.log(LOG_ERROR, F("SPARTON: ERROR - Start called when Sparton already running. Cleaning up."));
|
|
// closeFile();
|
|
sendLine(0);
|
|
off(true);
|
|
// TODO 2024 - delay? Consider safeDelay - although this is a rare occurecne (aka already running)
|
|
delay(500);
|
|
}
|
|
|
|
resetData();
|
|
step = WII5SPARTON_BOOT; stepWait = 0;
|
|
startWhen = 0; // Clear when binary start
|
|
startCapture = 0; // When we started this capture
|
|
lastWrite = 0; // Last time we wrote to SD Card successfully
|
|
// statsHighClear();
|
|
|
|
on(true);
|
|
}
|
|
|
|
void WII5Sparton::resetData() {
|
|
// Values used in collecting
|
|
serialSizeError = 0;
|
|
statsTimeError = 0;
|
|
|
|
blockNext = 0;
|
|
captureWriteMax = 0;
|
|
captureWriteMin = 1000;
|
|
captureWriteOver = 0;
|
|
|
|
// Current record.
|
|
#ifdef SPARTON_SIZES
|
|
for (uint8_t t = 0; t < SIZES_LENGTH; t++) {
|
|
sizes[t] = 0;
|
|
}
|
|
#endif
|
|
recordCount = 0; sendCount = 0;
|
|
|
|
// Averages
|
|
// avgAccelX.reset();
|
|
// avgAccelY.reset();
|
|
// avgAccelZ.reset();
|
|
// avgPoseX.reset();
|
|
// avgPoseY.reset();
|
|
// avgPoseZ.reset();
|
|
|
|
// Internal storage
|
|
#ifdef SPRTON_RECORD
|
|
recordReady = -1;
|
|
record[0].rec = 0; record[0].stamp = 0; record[0].stampActual = 0; record[0].stampErr = 0;
|
|
record[0].pose_x = NAN; record[0].pose_y = NAN; record[0].pose_z = NAN;
|
|
record[0].mag_x = NAN; record[0].mag_y = NAN; record[0].mag_z = NAN;
|
|
record[0].gyro_x = NAN; record[0].gyro_y = NAN; record[0].gyro_z = NAN;
|
|
record[0].accel_x = NAN; record[0].accel_y = NAN; record[0].accel_z = NAN;
|
|
record[1].rec = 0; record[0].stamp = 0; record[0].stampActual = 0; record[0].stampErr = 0;
|
|
record[1].pose_x = NAN; record[1].pose_y = NAN; record[1].pose_z = NAN;
|
|
record[1].mag_x = NAN; record[1].mag_y = NAN; record[1].mag_z = NAN;
|
|
record[1].gyro_x = NAN; record[1].gyro_y = NAN; record[1].gyro_z = NAN;
|
|
record[1].accel_x = NAN; record[1].accel_y = NAN; record[1].accel_z = NAN;
|
|
recordPrev.rec = 0; recordPrev.stamp = 0; recordPrev.stampActual = 0; recordPrev.stampErr = 0;
|
|
recordPrev.pose_x = NAN; recordPrev.pose_y = NAN; recordPrev.pose_z = NAN;
|
|
recordPrev.mag_x = NAN; recordPrev.mag_y = NAN; recordPrev.mag_z = NAN;
|
|
recordPrev.gyro_x = NAN; recordPrev.gyro_y = NAN; recordPrev.gyro_z = NAN;
|
|
recordPrev.accel_x = NAN; recordPrev.accel_y = NAN; recordPrev.accel_z = NAN;
|
|
#endif
|
|
|
|
console.log(LOG_INFO, F("SPARTON: Reset Sparton Data 0 - %d"), sizeof(binRecords));
|
|
memset(&binRecords, 0, sizeof(binRecords));
|
|
}
|
|
|
|
void WII5Sparton::info() {
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("SPARTON: info current=%d"), step);
|
|
recordCount = 0; sendCount = 0;
|
|
step = WII5SPARTON_INFO; stepWait = 0;
|
|
on();
|
|
}
|
|
|
|
void WII5Sparton::captureRun() {
|
|
// processMode = WII5SERIALPARSER_RECORD; // Capture fields
|
|
// last = WII5SERIALLAST_NONE;
|
|
step = WII5SPARTON_CAPTURE; stepWait = 0;
|
|
}
|
|
|
|
bool WII5Sparton::captureRunning() {
|
|
return (step == WII5SPARTON_CAPTURE);
|
|
}
|
|
|
|
bool WII5Sparton::captureFinished() {
|
|
return (
|
|
(step == WII5SPARTON_OFF)
|
|
);
|
|
}
|
|
|
|
// WAITING - It will wait here, basically forever, until you call captureRun()
|
|
// this is to allow preparation followed by remote trigger at a certain time.
|
|
// However - if it is past this - e.g. nowait - you could deadlock
|
|
bool WII5Sparton::captureWaiting() {
|
|
return (step == WII5SPARTON_PROGRAM_START);
|
|
}
|
|
|
|
void WII5Sparton::stop(bool force) {
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("SPARTON: cancel current=%d"), step);
|
|
if (step != WII5SPARTON_OFF) {
|
|
step = WII5SPARTON_CANCEL; stepWait = 0;
|
|
}
|
|
|
|
// Force to off state - done by sleep
|
|
if (force) {
|
|
powerOff(true);
|
|
step = WII5SPARTON_OFF;
|
|
}
|
|
}
|
|
|
|
void WII5Sparton::setRawCapture(bool in) {
|
|
rawCapture = in;
|
|
}
|
|
bool WII5Sparton::getRawCapture() {
|
|
return rawCapture;
|
|
}
|
|
|
|
void WII5Sparton::setConsoleDirect(bool in) {
|
|
consoleDirect = in;
|
|
}
|
|
bool WII5Sparton::getConsoleDirect() {
|
|
return consoleDirect;
|
|
}
|
|
|
|
bool WII5Sparton::isRunning() {
|
|
return running;
|
|
}
|
|
|
|
// on
|
|
void WII5Sparton::on(bool force) {
|
|
if (!running || force) {
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("SPARTON: power ON, baud=%lu"), (uint32_t)SerialIMU_Baud);
|
|
running = true;
|
|
cancel = false;
|
|
wii5Controller.shared5On();
|
|
#if POWER_IMU_SPARTON_PIN>0
|
|
powerOn();
|
|
#endif
|
|
// TODO: investigate whether pinMode(15, INPUT_PULLUP) was needed here
|
|
SerialIMU.begin(SerialIMU_Baud);
|
|
// SerialIMU.attachInterrupt(handleRxChar);
|
|
|
|
// stream = &SerialIMU;
|
|
// beginSerialManager();
|
|
}
|
|
}
|
|
|
|
void WII5Sparton::setBaudrate(uint32_t baud) {
|
|
console.log(LOG_DEBUG, F("SPARTON: Debug set baud=%lu"), baud);
|
|
SerialIMU.begin(baud);
|
|
}
|
|
|
|
// off
|
|
void WII5Sparton::off(bool force) {
|
|
if (running || force) {
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("SPARTON: power off"));
|
|
running = false;
|
|
SerialIMU.end();
|
|
// stream = NULL;
|
|
// endSerialManager();
|
|
displaySTATS();
|
|
#if POWER_IMU_SPARTON_PIN>0
|
|
powerOff();
|
|
#endif
|
|
wii5Controller.shared5Off();
|
|
}
|
|
}
|
|
|
|
// Sparton Things:
|
|
// - Turn Off
|
|
// - Start now
|
|
// - Start at X
|
|
|
|
bool firstBoot = true;
|
|
|
|
void WII5Sparton::sendLine(uint16_t l) {
|
|
programLine(l);
|
|
SerialIMU.println(wii5BufferString);
|
|
// TODO Configurable?
|
|
console.log(LOG_DEBUG, F("SPARTON: > %s"), wii5BufferString);
|
|
}
|
|
|
|
char c;
|
|
bool first;
|
|
uint8_t startReady;
|
|
void WII5Sparton::loop() {
|
|
first = (step != stepLast);
|
|
stepLast = step;
|
|
|
|
while (SerialIMU.available())
|
|
handleRxChar(SerialIMU.read());
|
|
|
|
// WII5SerialManager::loop();
|
|
switch (step) {
|
|
case WII5SPARTON_OFF:
|
|
off();
|
|
break;
|
|
|
|
case WII5SPARTON_BOOT:
|
|
on();
|
|
// Wait half a second then spin on
|
|
if (stepWait > 500) {
|
|
console.log(LOG_INFO, F("SPARTON: Startup"));
|
|
if (firstBoot) {
|
|
step = WII5SPARTON_BOOT_BAUD; stepWait = 0;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step boot->boot_baud"));
|
|
}
|
|
else {
|
|
step = WII5SPARTON_BOOT_STOP; stepWait = 0;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step boot->boot_stop"));
|
|
}
|
|
}
|
|
break;
|
|
|
|
// TODO Reset !!!
|
|
// - first - high, 500ms later, low, or reversed !
|
|
|
|
// First time boot - so lets check the baud rate is valid
|
|
case WII5SPARTON_BOOT_BAUD:
|
|
step = WII5SPARTON_BOOT_STOP; stepWait = 0;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step boot_baud->boot_stop"));
|
|
firstBoot = false;
|
|
break;
|
|
|
|
case WII5SPARTON_BOOT_STOP:
|
|
// First line is STOP
|
|
// processMode = WII5SERIALPARSER_NONE;
|
|
sendLine(0);
|
|
sendLine(0);
|
|
step = WII5SPARTON_BOOT_WAIT; stepWait = 0;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step boot_stop->boot_wait"));
|
|
break;
|
|
|
|
case WII5SPARTON_BOOT_WAIT:
|
|
// First time - clear the captture file
|
|
// Give it a full second to absorb all the junk
|
|
if (stepWait > 1000) {
|
|
SerialIMU.flush();
|
|
// processMode = WII5SERIALPARSER_NONE;
|
|
step = WII5SPARTON_READY; stepWait = 0;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step boot_wait->ready"));
|
|
}
|
|
break;
|
|
|
|
case WII5SPARTON_READY:
|
|
// Short Params (no gyro or mag)
|
|
programStart = 20; programEnd = 30; sendCount = 0;
|
|
// Experiment
|
|
// programStart = 50; programEnd = 60; sendCount = 0;
|
|
step = WII5SPARTON_PROGRAM_BASE; stepWait = 0;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step ready->program_base"));
|
|
break;
|
|
|
|
case WII5SPARTON_PROGRAM_BASE:
|
|
if (stepWait > 250) {
|
|
if (sendCount <= (programEnd - programStart)) {
|
|
// Send next line, inremement counter, and wait another 10 ms
|
|
sendLine(programStart + sendCount);
|
|
sendCount++;
|
|
stepWait = 0;
|
|
}
|
|
else {
|
|
step = WII5SPARTON_PROGRAM_START; stepWait = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WII5SPARTON_PROGRAM_START:
|
|
// // processMode = WII5SERIALPARSER_RECORD; // Capture fields
|
|
// // last = WII5SERIALLAST_NONE;
|
|
// step = WII5SPARTON_CAPTURE; stepWait = 0;
|
|
// Sits here, waiting forever, until we move it on manually when ready to run.
|
|
// But we stillshould add a timeout
|
|
/// SKIPT to capture
|
|
if (true) {
|
|
// processMode = WII5SERIALPARSER_RECORD; // Capture fields
|
|
// last = WII5SERIALLAST_NONE;
|
|
step = WII5SPARTON_CAPTURE; stepWait = 0;
|
|
}
|
|
break;
|
|
|
|
case WII5SPARTON_CAPTURE:
|
|
// Reset counters, so as not to loose first lot
|
|
if (first) {
|
|
console.log(LOG_INFO, F("SPARTON: Capturing"));
|
|
resetData();
|
|
timeStart = now();
|
|
}
|
|
|
|
// We do everything else in the gap between the records (TBC)
|
|
// TODO This has not been tested
|
|
else if (startWhen > 5) {
|
|
|
|
// TODO Fix hardcoded time 5 above
|
|
|
|
// TODO Fix hard coded 15 recods per block
|
|
|
|
// Do we need to wrtie a block of data
|
|
if ( (recordCount / 15) > blockNext) {
|
|
|
|
// TODO test if block too far
|
|
if ( (recordCount / 15) > (blockNext + 1) ) {
|
|
console.log(LOG_FATAL, F("Sparton: Block too far, all data probably lost. block=%lu, expected=%lu, records=%lu"),
|
|
blockNext,
|
|
recordCount / 15,
|
|
recordCount
|
|
);
|
|
}
|
|
|
|
captureWriteTime = 0;
|
|
if (sdBlock.dataIsOpen()) {
|
|
if (sdBlock.dataWrite(
|
|
&binRecords[(blockNext % 2) * 15],
|
|
15 * 33
|
|
)) {
|
|
lastWrite = 0;
|
|
// TODO No, slow
|
|
/*
|
|
console.log(LOG_INFO, F("Sparton: Block record write OK: block=%lu, records=%lu, binRecords=%d, size=%d"),
|
|
blockNext, recordCount,
|
|
int(&binRecords[(blockNext % 2) * 15]),
|
|
int(15 * 33)
|
|
);
|
|
*/
|
|
}
|
|
else {
|
|
// TODO No, slow
|
|
console.log(LOG_ERROR, F("Sparton: Block record write FAILED: %lu"), blockNext);
|
|
// At this point - we stop processing ! But need to log that.
|
|
console.log(LOG_ERROR, F("Sparton: CANCEL CAPTURE - Can't write to SD Card"));
|
|
stop();
|
|
}
|
|
}
|
|
else {
|
|
console.log(LOG_ERROR, F("Sparton: Block Data not open"));
|
|
// At this point - we stop processing ! But need to log that.
|
|
console.log(LOG_ERROR, F("Sparton: CANCEL CAPTURE - Can't write to SD Card"));
|
|
stop();
|
|
}
|
|
|
|
if (captureWriteTime > captureWriteMax)
|
|
captureWriteMax = captureWriteTime;
|
|
if (captureWriteTime < captureWriteMin)
|
|
captureWriteMin = captureWriteTime;
|
|
if (captureWriteTime > 5)
|
|
captureWriteOver++;
|
|
|
|
// TODO Catch errors?
|
|
|
|
blockNext++;
|
|
}
|
|
|
|
else if ((stepWait > 700) && ((recordCount % HZ) == 0) && (lastValidRecord < SPARTON_RECORDS) ) {
|
|
// console.printBinary((char*)&binBuffer[binBufferReady], sizeof(binBuffer[0]));
|
|
wii5Display.atDataSend(
|
|
// NOTE: Must not add the "@" - it isn't used in CRC calculation
|
|
// line,stamp,err,8000,0,0,accel:x,y,z,gyro:x,y,z,mag:x,y,z,pos:x,y,z
|
|
F("CaptureSummary"),
|
|
F("%lu/%lu,%d,%d,%d,%d,%d,%d,block=%lu/%lu"),
|
|
recordCount, recordTotal,
|
|
int(binRecords[lastValidRecord].accel_x),
|
|
int(binRecords[lastValidRecord].accel_y),
|
|
int(binRecords[lastValidRecord].accel_z),
|
|
int(binRecords[lastValidRecord].pose_x),
|
|
int(binRecords[lastValidRecord].pose_y),
|
|
int(binRecords[lastValidRecord].pose_z),
|
|
blockNext, (recordTotal / 15)
|
|
);
|
|
/*
|
|
Not recommended - 1 per second at 9600
|
|
wii5Display.statusSend(
|
|
F("Sparton"),
|
|
F("record=%lu/%lu,z=%d"),
|
|
recordCount, recordTotal,
|
|
int(binRecords[lastValidRecord].accel_z)
|
|
);
|
|
*/
|
|
stepWait = 0;
|
|
|
|
}
|
|
|
|
// New 2024 - Check if recorsds never increasing and report errors !
|
|
// No records but startTime > 2 minutes - FAILURE !
|
|
// TODO 2024 - This does NOT capture if the sparton fails half way through....
|
|
// What we want is no records for more than 2 minutes.
|
|
// Change this code so startCapture is something like lastWriteTime or lastRecordTime and check that isn't 2 minutes old
|
|
else if ( lastWrite > 120000) {
|
|
console.log(LOG_INFO, F("Sparton: Failure ! No writes for 2 minutes"));
|
|
snprintf_P(
|
|
wii5Commands.lastCommandMessage, sizeof(wii5Commands.lastCommandMessage),
|
|
PSTR("@Err,sparton,norecords")
|
|
);
|
|
wii5Commands.lastCommandCmd = 0;
|
|
wii5Communications.sendError();
|
|
step = WII5SPARTON_PROGRAM_STOP; stepWait = 0;
|
|
}
|
|
|
|
// Do we have enough?
|
|
else if (recordCount >= recordTotal) {
|
|
// If we are new block. OR half way through a block...
|
|
if (
|
|
sdBlock.dataIsOpen()
|
|
&& ((recordCount / 15) >= blockNext)
|
|
&& ((recordCount % 15) > 0)
|
|
) {
|
|
|
|
// If half way, blank the rest
|
|
if ((recordCount % 15) > 0) {
|
|
// Blank the other records - 0 or 15 start + left over to end
|
|
memset(&binRecords[((blockNext % 2) * 15) + (recordCount % 15)], 0, 33 * (15 - (recordCount % 15)));
|
|
}
|
|
|
|
if (!sdBlock.dataWrite(
|
|
&binRecords[(blockNext % 2) * 15],
|
|
15 * 33
|
|
)) {
|
|
console.log(LOG_ERROR, F("Sparton: Block record write FAILED: %lu"), blockNext);
|
|
}
|
|
|
|
blockNext++;
|
|
}
|
|
|
|
timeEnd = now();
|
|
console.log(LOG_INFO, F("SPARTON: Finished Capture. Records=%lu of %lu, block written=%lu"), recordCount, recordTotal, blockNext);
|
|
// processMode = WII5SERIALPARSER_NONE;
|
|
// last = WII5SERIALLAST_NONE;
|
|
// Display high speed stats
|
|
// statsHighDisplay();
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step capture->program_stop"));
|
|
step = WII5SPARTON_PROGRAM_STOP; stepWait = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WII5SPARTON_PROGRAM_STOP:
|
|
// TODO send the stop command
|
|
// programStart = 0; programEnd = 1;
|
|
// step = WII5SPARTON_PROGRAM_BASE; stepWait = 0;
|
|
// processMode = WII5SERIALPARSER_NONE;
|
|
sendLine(0);
|
|
step = WII5SPARTON_FINISH; stepWait = 0;
|
|
// last = WII5SERIALLAST_NONE;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step program_stop->finish"));
|
|
break;
|
|
|
|
case WII5SPARTON_CANCEL:
|
|
// Shut it down., power will go down at OFF
|
|
// closeFile();
|
|
sendLine(0);
|
|
step = WII5SPARTON_FINISH; stepWait = 0;
|
|
cancel = true;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step cancel->finish"));
|
|
break;
|
|
|
|
case WII5SPARTON_FINISH:
|
|
step = WII5SPARTON_OFF; stepWait = 0;
|
|
if (debug)
|
|
console.log(LOG_DEBUG, F("step finish->off"));
|
|
break;
|
|
|
|
// TODO DEBUG THIS !
|
|
case WII5SPARTON_INFO:
|
|
if (first) {
|
|
sendCount = 0;
|
|
}
|
|
else if (stepWait > 500) {
|
|
// Standard setup commands
|
|
if (sendCount <= 12) {
|
|
// Send next line, inremement counter, and wait another 10 ms
|
|
sendLine(sendCount);
|
|
sendCount++;
|
|
stepWait = 0;
|
|
}
|
|
|
|
// 13 = 30 = Version
|
|
else if (sendCount == 13) {
|
|
sendLine(80);
|
|
sendCount++;
|
|
stepWait = 0;
|
|
}
|
|
|
|
// 14 = 31 = Config XML
|
|
else if (sendCount == 14) {
|
|
if (stepWait > 3000) {
|
|
sendLine(81);
|
|
sendCount++;
|
|
stepWait = 0;
|
|
}
|
|
}
|
|
|
|
// Wait 5 seconds to end
|
|
else if (stepWait > 60000) {
|
|
step = WII5SPARTON_OFF; stepWait = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
console.log(LOG_FATAL, F("SPARTON: Default step... step=%d"), step);
|
|
step = WII5SPARTON_OFF;
|
|
// Power off
|
|
break;
|
|
}
|
|
|
|
while (SerialIMU.available())
|
|
handleRxChar(SerialIMU.read());
|
|
|
|
return;
|
|
}
|
|
|
|
void WII5Sparton::repl() {
|
|
uint8_t lastDot = 0;
|
|
console.log(LOG_FATAL, F("SPARTON: REPL. '...' to exit."));
|
|
|
|
start();
|
|
// SerialIMU.detachInterrupt();
|
|
while (1) {
|
|
if (SerialConsole.available()) {
|
|
char c = SerialConsole.read();
|
|
if (c == '.')
|
|
lastDot++;
|
|
else
|
|
lastDot = 0;
|
|
if (lastDot >= 3) {
|
|
SerialConsole.flush();
|
|
SerialIMU.flush();
|
|
stop();
|
|
console.log(LOG_FATAL, F("SPARTON: Completed REPL. User exit."));
|
|
return;
|
|
}
|
|
SerialIMU.write(c);
|
|
}
|
|
|
|
if (SerialIMU.available()) {
|
|
char c = SerialIMU.read() ;
|
|
SerialConsole.write(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void WII5Sparton::displaySTATS() {
|
|
wii5Display.atStatsSend(
|
|
F("Sparton"),
|
|
F("errorTime=%lu,errorSize=%lu,writeMax=%lu,writeMin=%lu,writeOver=%lu"),
|
|
statsTimeError, serialSizeError,
|
|
captureWriteMax, captureWriteMin, captureWriteOver
|
|
);
|
|
#ifdef SPARTON_SIZES
|
|
for (uint8_t t = 0; t < SIZES_LENGTH; t++) {
|
|
if (sizes[t] > 0) {
|
|
if (t == 0)
|
|
console.printf(F("# SPARTON Record Size: others = %d\r\n"), sizes[t]);
|
|
else
|
|
console.printf(F("# SPARTON Record Size: %d = %d\r\n"), t + SIZES_START, sizes[t]);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// TODO Totaly - this can be common wii5BufferString for all Serial Devices or even the Console
|
|
void WII5Sparton::programLine(uint16_t l) {
|
|
memset(wii5BufferString, 0, WII5_BUFFER_STRING);
|
|
|
|
// Main Programming
|
|
switch (l) {
|
|
|
|
// CODE = Wave Tank and original code using all things, including magnatometer
|
|
// PROGRAM=0..12, START=13..13, PROGRAM+START=0..13, STOP=0..0
|
|
case 0:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 0 set drop"));
|
|
break;
|
|
case 1:
|
|
// TODO: confirm whether the reset+wait sequence here is actually required
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("accelrange 2 set drop"));
|
|
break;
|
|
case 2:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop"));
|
|
break;
|
|
case 3:
|
|
if (binMode)
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 2 set drop"));
|
|
else
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 3 set drop"));
|
|
break;
|
|
case 4:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit cputime dvid@ set drop"));
|
|
break;
|
|
case 5:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit magp dvid@ set drop"));
|
|
break;
|
|
case 6:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit accelp dvid@ set drop"));
|
|
break;
|
|
case 7:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit roll dvid@ set drop"));
|
|
break;
|
|
case 8:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit pitch dvid@ set drop"));
|
|
break;
|
|
case 9:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit yaw dvid@ set drop"));
|
|
break;
|
|
case 10:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit gyrop dvid@ set drop"));
|
|
break;
|
|
case 11:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Trigger 6 set drop"));
|
|
break;
|
|
case 12:
|
|
if (HZ == 64) {
|
|
// 64Hz
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 156 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 64, setting 100us timer to 156"));
|
|
}
|
|
else if (HZ == 8) {
|
|
// 8Hz
|
|
// TODO
|
|
// strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop"));
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 1250 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250"));
|
|
}
|
|
else if (HZ == 1) {
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250"));
|
|
}
|
|
else {
|
|
console.log(LOG_FATAL, F("Invalid HZ for Sprton = %d"), HZ);
|
|
}
|
|
break;
|
|
case 13:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 1 set drop"));
|
|
break;
|
|
|
|
// CODE = Cut down code for WII5.1 Tas Delivery - no Gyro or Mag
|
|
// PROGRAM=20..29
|
|
case 20:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 0 set drop"));
|
|
break;
|
|
case 21:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop"));
|
|
break;
|
|
case 22:
|
|
if (binMode)
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 2 set drop"));
|
|
else
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 3 set drop"));
|
|
break;
|
|
case 23:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit cputime dvid@ set drop"));
|
|
break;
|
|
case 24:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit accelp dvid@ set drop"));
|
|
break;
|
|
case 25:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit roll dvid@ set drop"));
|
|
break;
|
|
case 26:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit pitch dvid@ set drop"));
|
|
break;
|
|
case 27:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit yaw dvid@ set drop"));
|
|
break;
|
|
case 28:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Trigger 6 set drop"));
|
|
break;
|
|
case 29:
|
|
if (HZ == 64) {
|
|
// 64Hz
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 156 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 64, setting 100us timer to 156"));
|
|
}
|
|
else if (HZ == 8) {
|
|
// 8Hz
|
|
// TODO
|
|
// strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop"));
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 1250 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250"));
|
|
}
|
|
else if (HZ == 1) {
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250"));
|
|
}
|
|
else {
|
|
console.log(LOG_FATAL, F("Invalid HZ for Sprton = %d"), HZ);
|
|
}
|
|
break;
|
|
case 30:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 1 set drop"));
|
|
break;
|
|
|
|
// PROGRAM=0..12, START=13..13, PROGRAM+START=0..13, STOP=0..0
|
|
case 50:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 0 set drop"));
|
|
break;
|
|
case 51:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("accelrange 2 set drop"));
|
|
break;
|
|
case 52:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Enables array[ 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ]array set drop"));
|
|
break;
|
|
case 53:
|
|
if (binMode)
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 2 set drop"));
|
|
else
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Format 3 set drop"));
|
|
break;
|
|
case 54:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit cputime dvid@ set drop"));
|
|
break;
|
|
case 55:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit roll dvid@ set drop"));
|
|
break;
|
|
case 56:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit pitch dvid@ set drop"));
|
|
break;
|
|
case 57:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0EnableBit yaw dvid@ set drop"));
|
|
break;
|
|
case 58:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0Trigger 6 set drop"));
|
|
break;
|
|
case 59:
|
|
if (HZ == 64) {
|
|
// 64Hz
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 156 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 64, setting 100us timer to 156"));
|
|
}
|
|
else if (HZ == 8) {
|
|
// 8Hz
|
|
// TODO
|
|
// strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop"));
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 1250 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250"));
|
|
}
|
|
else if (HZ == 1) {
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chanTimerX100us 10000 set drop"));
|
|
console.log(LOG_DEBUG, F("HZ 8, setting 100us timer to 1250"));
|
|
}
|
|
else {
|
|
console.log(LOG_FATAL, F("Invalid HZ for Sprton = %d"), HZ);
|
|
}
|
|
break;
|
|
case 60:
|
|
strcpy_P(wii5BufferString ,(PGM_P) F("chan0TriggerDivisor 1 set drop"));
|
|
break;
|
|
|
|
|
|
// Debugging / Logging
|
|
// 14-20
|
|
case 80:
|
|
strcpy_PF(wii5BufferString ,F("chan0TriggerDivisor 0 set drop"));
|
|
break;
|
|
case 81:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("name di."));
|
|
break;
|
|
case 82:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("serialnumber di."));
|
|
break;
|
|
case 83:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("VERSION di."));
|
|
break;
|
|
case 84:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("VERSION_M4 di."));
|
|
break;
|
|
case 85:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("gyroSampleRate di."));
|
|
break;
|
|
case 86:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("save_mask d.on"));
|
|
break;
|
|
case 87:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("db.print"));
|
|
break;
|
|
|
|
case 90:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("VERSION di."));
|
|
break;
|
|
|
|
case 91:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("chan0Enable."));
|
|
break;
|
|
|
|
case 100:
|
|
strcpy_P(wii5BufferString, (PGM_P) F("baud 8 set drop"));
|
|
break;
|
|
|
|
default:
|
|
console.log(LOG_FATAL, F("Sparton: request invalid programming string"));
|
|
wii5BufferString[0] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
|
|
WII5Sparton wii5Sparton;
|