-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathDataSaverSPI.h
More file actions
401 lines (345 loc) · 14.6 KB
/
DataSaverSPI.h
File metadata and controls
401 lines (345 loc) · 14.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
#ifndef DATASAVERSPI_H
#define DATASAVERSPI_H
#include "ArduinoHAL.h"
#include "data_handling/DataPoint.h"
#include "data_handling/DataSaver.h"
#include <array>
#include <cstdlib>
#include <limits>
constexpr uint32_t kMetadataStartAddress = 0x000000; // Start writing metadata at the beginning of flash
constexpr uint32_t kDataStartAddress = 0x001000; // Start writing data after 1 sector (4kB) of metadata
constexpr uint32_t kPostLaunchFlagAddress = 0x000000; // Address of the post-launch flag
constexpr uint32_t kLaunchStartAddressAddress = 0x000001; // Address of the launch start address (32 bits)
constexpr uint8_t kPostLaunchFlagTrue = 0x00; // Flag to indicate post-launch mode is active
constexpr uint8_t kPostLaunchFlagFalse = 0x01; // Flag to indicate post-launch mode is not active
constexpr uint8_t kEmptyPageValue = 0xFF;
#pragma pack(push, 1) // Pack the struct to avoid padding between the name and datas
typedef struct { // NOLINT(altera-struct-pack-align)
uint8_t name;
float data;
} Record_t;
typedef struct { // NOLINT(altera-struct-pack-align)
uint8_t name;
uint32_t timestamp_ms;
} TimestampRecord_t;
#pragma pack(pop) // Stop packing from here on out
/**
* @brief SPI flash implementation of IDataSaver with timestamp compression.
* @note When to use: onboard non-volatile logging where SD cards are
* impractical and data may need to survive power loss or long recovery.
*/
class DataSaverSPI : public IDataSaver {
public:
static constexpr size_t kBufferSize_bytes = 256;
/**
* @brief Construct a new DataSaverSPI object
*
* @param timestampInterval_ms Interval in ms at which timestamps are stored
* @param flash Pointer to an initialized Adafruit_SPIFlash object
*
* @note timestampInterval_ms must be < 65535 (about 1 minute)
*/
DataSaverSPI(uint16_t timestampInterval_ms, Adafruit_SPIFlash *flash);
/**
* @brief Saves a DataPoint to SPI flash.
*
* If the new data point’s timestamp differs from the last written
* timestamp by more than timestampInterval_ms_, the new timestamp is also
* written to flash. Otherwise, the timestamp is omitted (to save space).
*
* @param dataPoint The data point to save
* @param name An 8-bit “identifier” for the data point (could be a sensor ID)
* @return int 0 on success, 1 when writes are blocked by post-launch state,
* and -1 on write/buffer error.
*/
int saveDataPoint(const DataPoint& dataPoint, uint8_t name) override;
/**
* @brief Persist a bare timestamp entry to flash.
* @param timestamp_ms Timestamp in milliseconds to record.
* @note When to use: emitted internally when gaps exceed
* timestampInterval_ms_, or explicitly in tests.
* @return int 0 on success, 1 when writes are blocked by post-launch state,
* and -1 on non-post-launch write/buffer error.
*/
int saveTimestamp(uint32_t timestamp_ms);
/**
* @brief Initialize the flash chip and metadata.
* @note When to use: call during setup before any saveDataPoint usage.
* @return true when flash is initialized and writable for logging.
* @return false when initialization fails or chip is already in post-launch mode.
*/
virtual bool begin() override;
/**
* @brief Returns whether the metadata from the flash chip
* indicates that it contains post-launch data that hasn't been
* dumped yet. Call `clearPostLaunchMode()` to clear this flag.
* once you are confident that you have the data you want dumped
* to a more permanent storage or another machine.
*
* Will also update the post-launch mode flag.
*
* @return true if post-launch mode is active
* @return false if post-launch mode is not active
*/
bool isPostLaunchMode();
/**
* @brief Clears the post-launch mode flag on the flash chip
* **WARNING: This will allow the data to be overwritten
* Only call this after you have dumped the data to a more
* permanent storage or another machine.
*/
void clearPostLaunchMode();
/**
* @brief Call this when launch is detected to set the post-launch flag and
* prevent any data from being overwritten until the flag is cleared.
* All data written from this point on is "post-launch" data which is
* sacred and should not be overwritten until it has been dumped.
*
* Sets the post-launch mode flag to true on the flash chip.
* Records the address at which the launch was detected and ensures that it's
* not overwritten
*
* Data is saved in a circular fashion but once the address where the launch detected
* is reached this will stop saving data entirely. Also keeps 1 minute of data from before
* the launch was detected (b/c launch can be detected late and we have extra room)
*
* The rocket may not be recovered for several hours, this prevents the cool launch data
* from being overwitten with boring laying-on-the-ground data.
* @param launchTimestamp_ms Timestamp at which launch was detected.
*/
void launchDetected(uint32_t launchTimestamp_ms);
/**
* @brief Stream all recorded data to a serial connection.
* @param serial Output stream.
* @param ignoreEmptyPages Skip pages that appear unwritten.
* @note When to use: post-flight data retrieval before erasing or
* redeploying the flash chip.
*/
void dumpData(Stream &serial, bool ignoreEmptyPages);
/**
* @brief Reset in-memory pointers without erasing flash contents.
* @note When to use: restart logging logic while preserving prior data on
* the chip.
*/
void clearInternalState();
/**
* @brief Erase the entire flash chip to start fresh.
* @note When to use: Never needs to be used in normal operation. After
* about 1 hour of runtime, the entire chip is overwritten anyways.
* Can be used in testing to reset the chip to a known state and then
* test which bytes are written
*/
void eraseAllData();
/**
* @brief Returns the last timestamp that was actually written to flash.
* @return Last persisted timestamp in milliseconds.
*/
uint32_t getLastTimestamp() const {
return lastTimestamp_ms_;
}
/**
* @brief Returns the last DataPoint that was written (not necessarily
* including timestamp, just the data chunk).
* @return Copy of the last cached data point.
*/
DataPoint getLastDataPoint() const {
return lastDataPoint_;
}
/**
* @brief Returns the launch-protected address computed during launch detection.
* @return Address in flash used as post-launch protection boundary.
*/
uint32_t getLaunchWriteAddress() const {
return launchWriteAddress_;
}
/**
* @brief Returns the next flash address where a full page will be written.
* @return Next write address in flash.
*/
uint32_t getNextWriteAddress() const {
return nextWriteAddress_;
}
/**
* @brief Resets the timestamp to 0. Can be used to start a new flight
* during runtime. Useful for testing.
*/
void resetTimestamp() {
lastTimestamp_ms_ = 0;
}
/**
* @brief Returns whether the flash chip is in post-launch mode
* without updating the post-launch mode flag or reading from flash.
* @return true when in post-launch mode; otherwise false.
*/
bool quickGetPostLaunchMode() {
return this->postLaunchMode_;
}
private:
// Interval at which to store the timestamp in flash.
uint16_t timestampInterval_ms_;
// The last timestamp we actually wrote to flash.
uint32_t lastTimestamp_ms_;
// The timestamp this module was given for launch
uint32_t launchTimestamp_ms_;
// The last data point written
DataPoint lastDataPoint_;
// Flash driver
Adafruit_SPIFlash *flash_;
// Next address in flash at which to write.
uint32_t nextWriteAddress_;
// Address at which launch was detected
uint32_t launchWriteAddress_;
bool postLaunchMode_;
private:
/**
* @brief Helper to write a block of bytes to flash at the current
* write address and advance that pointer.
* @param data Pointer to bytes to write.
* @param length Number of bytes to write.
* @return true on successful write and pointer advance; otherwise false.
*/
bool writeToFlash(const uint8_t* data, size_t length);
/**
* @brief Helper to read a block of bytes from flash (updates read pointer externally).
* @param readAddress Input/output flash address. Advanced by `length` on success.
* @param buffer Output byte buffer.
* @param length Number of bytes to read.
* @return true on successful read and pointer advance; otherwise false.
*/
bool readFromFlash(uint32_t& readAddress, uint8_t* buffer, size_t length);
// Write buffer to improve write performance
uint8_t buffer_[kBufferSize_bytes] = {};
size_t bufferIndex_ = 0;
uint32_t bufferFlushes_ = 0; // Keep track of how many times the buffer has been flushed
public:
/**
* @brief Returns the current buffer index
* Useful for testing
* @return Number of bytes currently buffered.
*/
size_t getBufferIndex() const {
return bufferIndex_;
}
/**
* @brief Returns the current buffer flush count
* Useful for testing
* @return Number of successful page flushes.
*/
uint32_t getBufferFlushes() const {
return bufferFlushes_;
}
/**
* @brief Returns whether writes have been stopped by post-launch protection.
* @return true if chip-full post-launch protection latch is set; otherwise false.
*/
bool getIsChipFullDueToPostLaunchProtection() const {
return isChipFullDueToPostLaunchProtection_;
}
/**
* @brief Returns whether startup detected existing post-launch mode metadata.
* @return true if rebooted in post-launch mode; otherwise false.
*/
bool getRebootedInPostLaunchMode() const {
return rebootedInPostLaunchMode_;
}
#ifdef UNIT_TEST
/**
* @brief Test-only helper to override internal post-launch state.
* Resets in-memory state first so the injected state is
* self-consistent and does not depend on prior test writes.
* @param nextWriteAddress_in Next write address to inject.
* @param launchWriteAddress_in Launch-protected address to inject.
* @param postLaunchMode_in Post-launch mode flag to inject.
*/
void setPostLaunchStateForTest(uint32_t nextWriteAddress_in,
uint32_t launchWriteAddress_in,
bool postLaunchMode_in) {
clearInternalState();
nextWriteAddress_ = nextWriteAddress_in;
launchWriteAddress_ = launchWriteAddress_in;
postLaunchMode_ = postLaunchMode_in;
isChipFullDueToPostLaunchProtection_ = false;
}
#endif
private:
/**
* @brief Normalizes an address into the data region when crossing flash end.
* @param address Candidate flash address.
* @return `address` when in range, otherwise `kDataStartAddress`.
*/
uint32_t normalizeDataAddress(uint32_t address) const;
/**
* @brief Checks if a sector is protected by post-launch rules.
* @param sectorNumber Sector index to evaluate.
* @return true when sector contains `launchWriteAddress_` and post-launch mode is active.
* @return false otherwise.
*/
bool isProtectedLaunchSector(uint32_t sectorNumber) const;
/**
* @brief Outcome of attempting to erase a sector before writing into it.
*/
enum class SectorEraseResult {
kErased,
kProtectedSectorLatched,
kFlashEraseFailed
};
/**
* @brief Erases a sector for writing and latches post-launch protection if blocked.
* @param sectorNumber Sector index to erase.
* @return SectorEraseResult::kErased on successful erase.
* @return SectorEraseResult::kProtectedSectorLatched when sector is
* post-launch protected (and chip-full is latched).
* @return SectorEraseResult::kFlashEraseFailed when flash erase fails.
*/
SectorEraseResult eraseSectorForWriteAndLatchOnProtection(uint32_t sectorNumber);
/**
* @brief Checks if the upcoming write window would violate post-launch protection.
* @return true when writing must stop and chip-full protection is latched.
* @return false when write may proceed.
*/
bool shouldStopForPostLaunchWindow();
/**
* @brief Flushes the buffer to flash.
*
* Returns 0 on success
* Returns 1 if the buffer is empty
* Returns -1 on error
*/
int flushBuffer();
/**
* @brief Adds data to the buffer
*
* @param data The data to add
* @param length The length of the data
* @return int 0 on success; non-zero on error
*/
int addDataToBuffer(const uint8_t* data, size_t length);
// Overloaded functions to add data to the buffer from a Record_t or TimestampRecord_t
// More efficient than callling addDataToBuffer with each part of the record
/**
* @brief Adds a 5-byte record payload to the page buffer.
* @param record Pointer to packed record bytes.
* @return int 0 on success; -1 on flush/buffer error.
*/
int addRecordToBuffer(Record_t * record) {
return addDataToBuffer(reinterpret_cast<const uint8_t*>(record), 5);
}
/**
* @brief Adds a 5-byte timestamp record payload to the page buffer.
* @param record Pointer to packed timestamp record bytes.
* @return int 0 on success; -1 on flush/buffer error.
*/
int addRecordToBuffer(TimestampRecord_t * record) {
return addDataToBuffer(reinterpret_cast<const uint8_t*>(record), 5);
}
// The chip will keep overwriting data forever unless post launch data is being protected.
// Once it wraps back around to the launch write address, it will stop writing data.
bool isChipFullDueToPostLaunchProtection_;
// If the flight computer boots and is already in post launch mode, do not write to flash.
// Calling clearPostLaunchMode() will allow writing to flash again after a reboot.
bool rebootedInPostLaunchMode_ = false;
// Tracks which sector has already been pre-erased for the next boundary write.
// UINT32_MAX means "no prepared sector".
uint32_t preparedSectorNumber_ = std::numeric_limits<uint32_t>::max();
};
#endif // DATA_SAVER_SPI_H