Avionics
Core avionics package for CURE flight computers
Loading...
Searching...
No Matches
DataSaverSPI.cpp
Go to the documentation of this file.
3
4#include <cstring>
5
6DataSaverSPI::DataSaverSPI(uint16_t timestampInterval_ms,
8 : timestampInterval_ms(timestampInterval_ms),
9 flash(flash),
10 nextWriteAddress(DATA_START_ADDRESS),
11 bufferIndex(0),
12 lastTimestamp_ms(0),
13 launchTimestamp_ms(0),
14 postLaunchMode(false),
15 launchWriteAddress(0),
16 isChipFullDueToPostLaunchProtection(false) {
18}
19
20int DataSaverSPI::saveDataPoint(const DataPoint& dataPoint, uint8_t name) {
21 if (rebootedInPostLaunchMode || isChipFullDueToPostLaunchProtection) {
22 return 1; // Do not save if we rebooted in post-launch mode
23 }
24
25 // Write a timestamp automatically if enough time has passed since the last one
26 uint32_t const timestamp = dataPoint.timestamp_ms;
27 if (timestamp - lastTimestamp_ms > timestampInterval_ms) {
28 if (saveTimestamp(timestamp) < 0) {
29 return -1;
30 }
31 }
32
33 Record_t record = {name, dataPoint.data};
34 if (addRecordToBuffer(&record) < 0) {
35 return -1;
36 }
37
38 lastDataPoint = dataPoint;
39 return 0;
40}
41
42int DataSaverSPI::saveTimestamp(uint32_t timestamp_ms){
43 if (rebootedInPostLaunchMode || isChipFullDueToPostLaunchProtection) {
44 return 1; // Do not save if we rebooted in post-launch mode
45 }
46
47 TimestampRecord_t timeStampRecord = {TIMESTAMP, timestamp_ms};
48 if (addRecordToBuffer(&timeStampRecord) != 0) {
49 return -1;
50 }
51
52 lastTimestamp_ms = timestamp_ms;
53 return 0;
54}
55
56int DataSaverSPI::addDataToBuffer(const uint8_t* data, size_t length) {
57 if (bufferIndex + length > BUFFER_SIZE) {
58 // Flush the buffer
59 if (flushBuffer() < 0) {
60 return -1;
61 }
62 }
63
64 // Copy the data into the buffer
65 memcpy(buffer + bufferIndex, data, length);
66 bufferIndex += length;
67 return 0;
68}
69
70// Write the entire buffer to flash
71int DataSaverSPI::flushBuffer() {
72 if (bufferIndex == 0) {
73 return 1; // Nothing to flush
74 }
75
76 // Check if we need to wrap around
77 if (nextWriteAddress + bufferIndex > flash->size()) {
78 // Wrap around
79 nextWriteAddress = DATA_START_ADDRESS;
80 }
81
82 // Check that we haven't wrapped around to the launch address while in post-launch mode
83 if (postLaunchMode && nextWriteAddress <= launchWriteAddress && nextWriteAddress + BUFFER_SIZE * 2 > launchWriteAddress) {
84 isChipFullDueToPostLaunchProtection = true;
85 return -1; // Indicate no write due to post-launch protection
86 }
87
88 if (nextWriteAddress % SFLASH_SECTOR_SIZE == 0) {
89 if (!flash->eraseSector(nextWriteAddress / SFLASH_SECTOR_SIZE)) {
90 return -1;
91 }
92 }
93
94 // Write 1 page of data
95 if (!flash->writeBuffer(nextWriteAddress, buffer, BUFFER_SIZE)) {
96 return -1;
97 }
98
99 nextWriteAddress += BUFFER_SIZE; // keep it aligned to the buffer size or page size
100 bufferIndex = 0; // Reset the buffer
101 bufferFlushes++;
102 return 0;
103}
104
105
107 if (flash == nullptr) {
108 return false;
109 }
110 if (!flash->begin()) {
111 return false;
112 }
113
114 this->postLaunchMode = isPostLaunchMode();
115 if (postLaunchMode) {
116 // If we are already in post-launch mode, then don't write to flash at all
117 rebootedInPostLaunchMode = true;
118 return false;
119 }
120
121 return true;
122}
123
125 uint8_t flag = 0;
126 flash->readBuffer(POST_LAUNCH_FLAG_ADDRESS, &flag, sizeof(flag));
127 this->postLaunchMode = (flag == POST_LAUNCH_FLAG_TRUE);
128 return this->postLaunchMode;
129}
130
132 flash->eraseSector(METADATA_START_ADDRESS / SFLASH_SECTOR_SIZE);
133
134 uint8_t flag = POST_LAUNCH_FLAG_FALSE;
135 flash->writeBuffer(POST_LAUNCH_FLAG_ADDRESS, &flag, sizeof(flag));
137}
138
139void DataSaverSPI::dumpData(Stream &serial, bool ignoreEmptyPages) { //NOLINT(readability-function-cognitive-complexity)
140 uint32_t readAddress = DATA_START_ADDRESS; //NOLINT(cppcoreguidelines-init-variables) //NOLINT(misc-const-correctness)
141 // For each page write 51 sets of 5 bytes to serial with a newline
142 std::array<uint8_t, SFLASH_PAGE_SIZE> buffer; //NOLINT(cppcoreguidelines-init-variables)
143 size_t recordSize = sizeof(Record_t); //NOLINT(cppcoreguidelines-init-variables)
144 size_t numRecordsPerPage = SFLASH_PAGE_SIZE / recordSize; //NOLINT(cppcoreguidelines-init-variables)
145
146 // If not in post-launch mode, erase the next sector after nextWriteAddress
147 // This ensures that we don't accidentally dump old data from previous flights
148 // If ignoreEmptyPages is true, then we don't need to erase the next sector
149 if (!postLaunchMode && !ignoreEmptyPages) {
150 flash->eraseSector(nextWriteAddress / SFLASH_SECTOR_SIZE + 1);
151 }
152
153 // To ensure it's lined-up let's set a '\n' , '\r' and a 's' at the start
154 serial.write('a');
155 serial.write('b');
156 serial.write('c');
157 serial.write('d');
158 serial.write('e');
159 serial.write('f');
160
161 bool done = false;
162 bool timedOut = false;
163 bool stoppedFromEmptyPage = false;
164 bool badRead = false;
165
166 while (readAddress < flash->size()) {
167 if (!readFromFlash(readAddress, buffer.data(), SFLASH_PAGE_SIZE)) {
168 badRead = true;
169 return;
170 }
171
172 // If the first name of this page is 255 then break
173 if (buffer[0] == EMPTY_PAGE) {
174 if (ignoreEmptyPages) {
175 continue;
176 }
177 done = true;
178 stoppedFromEmptyPage = true;
179 break;
180 }
181
182 // At the start of each page, write some alignment characters
183 std::array<uint8_t, 3> startLine = {'l', 's', 'h'};
184 serial.write(startLine.data(), 3);
185
186
187
188 for (size_t i = 0; i < numRecordsPerPage; i++) { //NOLINT(cppcoreguidelines-init-variables)
189
190 serial.write(buffer.data() + i * recordSize, recordSize);
191 // serial.write('\n');
192 }
193
194 // Wait for a 'n' character to be received before continuing (10 second timeout)
195 uint32_t const timeout = millis() + 10000;
196 while (serial.read() != 'n') {
197 if (millis() > timeout) {
198 timedOut = true;
199 return;
200 }
201 }
202
203 }
204
205 #pragma unroll
206 for (int i = 0; i < BUFFER_SIZE; i++){
207 std::array<uint8_t, 3> doneLine = {'E', 'O', 'F'};
208 serial.write(doneLine.data(), doneLine.size());
209 if (done){
210 serial.write('D');
211 }
212 if (timedOut){
213 serial.write('T');
214 }
215 if (stoppedFromEmptyPage){
216 serial.write('P');
217 }
218 if (badRead){
219 serial.write('B');
220 }
221 if (readAddress >= flash->size()){
222 serial.write('F');
223 }
224 }
225}
226
228 bufferIndex = 0;
229 memset(buffer, 0, BUFFER_SIZE);
230 lastDataPoint = {0, 0};
231 nextWriteAddress = DATA_START_ADDRESS;
232 lastTimestamp_ms = 0;
233 postLaunchMode = false;
234 launchWriteAddress = 0;
235 bufferFlushes = 0;
236 isChipFullDueToPostLaunchProtection = false;
237}
238
240 flash->eraseChip();
242
244
245 // Clear the launchWriteAddress
246 launchWriteAddress = 0;
247
248}
249
250void DataSaverSPI::launchDetected(uint32_t launchTimestamp_ms) {
251 this->launchTimestamp_ms = launchTimestamp_ms;
252
253 // 0) Stop if we are already in post-launch mode
254 if (postLaunchMode) {
255 return;
256 }
257
258 // 0.5) Clear the metadata sector to avoid 0 --> 1 inabilites
259 flash->eraseSector(METADATA_START_ADDRESS / SFLASH_SECTOR_SIZE);
260
261 // 1) Set the post-launch flag in metadata so we don't overwrite post-launch data.
262 uint8_t flag = POST_LAUNCH_FLAG_TRUE;
263 flash->writeBuffer(POST_LAUNCH_FLAG_ADDRESS, &flag, sizeof(flag));
264 postLaunchMode = true;
265
266 // 2) Compute how many bytes we want to roll back to capture ~1 minute of pre-launch data.
267 // If you always store timestamps + name + DataPoint, factor that in:
268 //
269 // struct DataRecord {
270 // uint8_t name;
271 // DataPoint data;
272 // // occasionally 4 more bytes for a timestamp if we cross the interval threshold
273 // };
274 //
275 // For simplicity, let's assume worst-case all data points have the 4-byte timestamp:
276 // size_t recordSize = sizeof(uint32_t) // timestamp
277 // + sizeof(uint8_t) // name
278 // + sizeof(DataPoint);
279 //
280 // If you only occasionally store the timestamp, you might want a more nuanced approach.
281 //
282 size_t recordSize = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(DataPoint); //NOLINT(cppcoreguidelines-init-variables)
283 uint32_t const oneMinuteInMs = 60000;
284 uint32_t const dataPointsPerMinute = oneMinuteInMs / timestampInterval_ms;
285 uint32_t rollbackBytes = dataPointsPerMinute * recordSize;
286
287 // 3) Clamp rollbackBytes to something reasonable. We must not exceed
288 // the usable flash region (from address=1 to address=flash->size()-1).
289 uint32_t const maxUsable = flash->size() - DATA_START_ADDRESS;
290 if (rollbackBytes > maxUsable) {
291 // If we can't keep an entire minute, just keep as much as we can
292 rollbackBytes = maxUsable;
293 }
294
295 // 4) Next, we do ring-buffer math to find our new launchWriteAddress
296 // which is "1 minute's worth of data behind nextWriteAddress" *in a circular sense*.
297
298 // Because nextWriteAddress can be anywhere in [1, flash->size()-1],
299 // let’s do a safe modular subtraction:
300 //
301 // newAddr = ( nextWriteAddress + flash->size() - rollbackBytes )
302 // % flash->size()
303 //
304 // Then we ensure it’s never 0 because 0 is used for metadata.
305
306 uint32_t const sizeOfFlash = flash->size();
307
308 // Make sure we aren't in the metadata region
309 if (nextWriteAddress < DATA_START_ADDRESS) {
310 nextWriteAddress = DATA_START_ADDRESS;
311 }
312
313 // Use 64-bit to avoid any negative wrap during the subtraction.
314 int64_t potentialAddr = static_cast<int64_t>(nextWriteAddress)
315 + static_cast<int64_t>(sizeOfFlash) // ensure positivity
316 - static_cast<int64_t>(rollbackBytes);
317
318 // Modulo by sizeOfFlash to bring it back into [0, sizeOfFlash-1].
319 potentialAddr = potentialAddr % sizeOfFlash;
320
321 // If result is 0 or negative after mod, or less than DATA_START_ADDRESS, add sizeOfFlash
322 if (potentialAddr <= 0 || potentialAddr < DATA_START_ADDRESS) {
323 potentialAddr += sizeOfFlash;
324 }
325
326
327 launchWriteAddress = static_cast<uint32_t>(potentialAddr);
328
329 std::array<uint8_t, sizeof(launchWriteAddress)> bytes;
330 std::memcpy(bytes.data(), &launchWriteAddress, sizeof(launchWriteAddress));
331 flash->writeBuffer(LAUNCH_START_ADDRESS_ADDRESS, bytes.data(), bytes.size());
332
333}
334
335bool DataSaverSPI::writeToFlash(const uint8_t* data, size_t length) {
336 if (!flash->writeBuffer(nextWriteAddress, data, length)) {
337 return false;
338 }
339 nextWriteAddress += length;
340 return true;
341}
342
343bool DataSaverSPI::readFromFlash(uint32_t& readAddress, uint8_t* buffer, size_t length) {
344 if (!flash->readBuffer(readAddress, buffer, length)) {
345 return false;
346 }
347 readAddress += length;
348 return true;
349}
#define SFLASH_PAGE_SIZE
#define SFLASH_SECTOR_SIZE
#define TIMESTAMP
Definition DataNames.h:25
#define POST_LAUNCH_FLAG_TRUE
#define DATA_START_ADDRESS
constexpr uint8_t EMPTY_PAGE
#define LAUNCH_START_ADDRESS_ADDRESS
#define POST_LAUNCH_FLAG_FALSE
#define METADATA_START_ADDRESS
#define POST_LAUNCH_FLAG_ADDRESS
bool writeBuffer(uint32_t address, const uint8_t *buffer, size_t length)
Timestamped float measurement container.
Definition DataPoint.h:11
float data
Definition DataPoint.h:14
uint32_t timestamp_ms
Definition DataPoint.h:13
DataSaverSPI(uint16_t timestampInterval_ms, Adafruit_SPIFlash *flash)
Construct a new DataSaverSPI object.
static constexpr size_t BUFFER_SIZE
int saveDataPoint(const DataPoint &dataPoint, uint8_t name) override
Saves a DataPoint to SPI flash.
void clearInternalState()
Resets all internal state values (buffer, lastDataPoint, nextWriteAddress, lastTimestamp_ms) Does not...
bool isPostLaunchMode()
Returns whether the metadata from the flash chip indicates that it contains post-launch data that has...
void clearPostLaunchMode()
Clears the post-launch mode flag on the flash chip **WARNING: This will allow the data to be overwrit...
void eraseAllData()
Clears/erases the entire flash chip to start fresh.
int saveTimestamp(uint32_t timestamp_ms)
Persist a bare timestamp entry to flash.
void dumpData(Stream &serial, bool ignoreEmptyPages)
Dumps all data from flash to Serial.
virtual bool begin() override
Initialize the flash chip and metadata.
void launchDetected(uint32_t launchTimestamp_ms)
Call this when launch is detected to set the post-launch flag and prevent any data from being overwri...
virtual size_t write(uint8_t byte)
Definition serial_mock.h:77
virtual int read()
Definition serial_mock.h:74