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));
136
137 postLaunchMode = false;
138}
139
140void DataSaverSPI::dumpData(Stream &serial, bool ignoreEmptyPages) { //NOLINT(readability-function-cognitive-complexity)
141 uint32_t readAddress = DATA_START_ADDRESS; //NOLINT(cppcoreguidelines-init-variables) //NOLINT(misc-const-correctness)
142 // For each page write 51 sets of 5 bytes to serial with a newline
143 std::array<uint8_t, SFLASH_PAGE_SIZE> buffer; //NOLINT(cppcoreguidelines-init-variables)
144 size_t recordSize = sizeof(Record_t); //NOLINT(cppcoreguidelines-init-variables)
145 size_t numRecordsPerPage = SFLASH_PAGE_SIZE / recordSize; //NOLINT(cppcoreguidelines-init-variables)
146
147 // If not in post-launch mode, erase the next sector after nextWriteAddress
148 // This ensures that we don't accidentally dump old data from previous flights
149 // If ignoreEmptyPages is true, then we don't need to erase the next sector
150 if (!postLaunchMode && !ignoreEmptyPages) {
151 flash->eraseSector(nextWriteAddress / SFLASH_SECTOR_SIZE + 1);
152 }
153
154 // To ensure it's lined-up let's set a '\n' , '\r' and a 's' at the start
155 serial.write('a');
156 serial.write('b');
157 serial.write('c');
158 serial.write('d');
159 serial.write('e');
160 serial.write('f');
161
162 bool done = false;
163 bool timedOut = false;
164 bool stoppedFromEmptyPage = false;
165 bool badRead = false;
166
167 while (readAddress < flash->size()) {
168 if (!readFromFlash(readAddress, buffer.data(), SFLASH_PAGE_SIZE)) {
169 badRead = true;
170 return;
171 }
172
173 // If the first name of this page is 255 then break
174 if (buffer[0] == EMPTY_PAGE) {
175 if (ignoreEmptyPages) {
176 continue;
177 }
178 done = true;
179 stoppedFromEmptyPage = true;
180 break;
181 }
182
183 // At the start of each page, write some alignment characters
184 std::array<uint8_t, 3> startLine = {'l', 's', 'h'};
185 serial.write(startLine.data(), 3);
186
187
188
189 for (size_t i = 0; i < numRecordsPerPage; i++) { //NOLINT(cppcoreguidelines-init-variables)
190
191 serial.write(buffer.data() + i * recordSize, recordSize);
192 // serial.write('\n');
193 }
194
195 // Wait for a 'n' character to be received before continuing (10 second timeout)
196 uint32_t const timeout = millis() + 10000;
197 while (serial.read() != 'n') {
198 if (millis() > timeout) {
199 timedOut = true;
200 return;
201 }
202 }
203
204 }
205
206 #pragma unroll
207 for (int i = 0; i < BUFFER_SIZE; i++){
208 std::array<uint8_t, 3> doneLine = {'E', 'O', 'F'};
209 serial.write(doneLine.data(), doneLine.size());
210 if (done){
211 serial.write('D');
212 }
213 if (timedOut){
214 serial.write('T');
215 }
216 if (stoppedFromEmptyPage){
217 serial.write('P');
218 }
219 if (badRead){
220 serial.write('B');
221 }
222 if (readAddress >= flash->size()){
223 serial.write('F');
224 }
225 }
226}
227
229 bufferIndex = 0;
230 memset(buffer, 0, BUFFER_SIZE);
231 lastDataPoint = {0, 0};
232 nextWriteAddress = DATA_START_ADDRESS;
233 lastTimestamp_ms = 0;
234 postLaunchMode = false;
235 launchWriteAddress = 0;
236 bufferFlushes = 0;
237 isChipFullDueToPostLaunchProtection = false;
238}
239
241 flash->eraseChip();
243
245
246 // Clear the launchWriteAddress
247 launchWriteAddress = 0;
248
249}
250
251void DataSaverSPI::launchDetected(uint32_t launchTimestamp_ms) {
252 this->launchTimestamp_ms = launchTimestamp_ms;
253
254 // 0) Stop if we are already in post-launch mode
255 if (postLaunchMode) {
256 return;
257 }
258
259 // 0.5) Clear the metadata sector to avoid 0 --> 1 inabilites
260 flash->eraseSector(METADATA_START_ADDRESS / SFLASH_SECTOR_SIZE);
261
262 // 1) Set the post-launch flag in metadata so we don't overwrite post-launch data.
263 uint8_t flag = POST_LAUNCH_FLAG_TRUE;
264 flash->writeBuffer(POST_LAUNCH_FLAG_ADDRESS, &flag, sizeof(flag));
265 postLaunchMode = true;
266
267 // 2) Compute how many bytes we want to roll back to capture ~1 minute of pre-launch data.
268 // If you always store timestamps + name + DataPoint, factor that in:
269 //
270 // struct DataRecord {
271 // uint8_t name;
272 // DataPoint data;
273 // // occasionally 4 more bytes for a timestamp if we cross the interval threshold
274 // };
275 //
276 // For simplicity, let's assume worst-case all data points have the 4-byte timestamp:
277 // size_t recordSize = sizeof(uint32_t) // timestamp
278 // + sizeof(uint8_t) // name
279 // + sizeof(DataPoint);
280 //
281 // If you only occasionally store the timestamp, you might want a more nuanced approach.
282 //
283 size_t recordSize = sizeof(uint32_t) + sizeof(uint8_t) + sizeof(DataPoint); //NOLINT(cppcoreguidelines-init-variables)
284 uint32_t const oneMinuteInMs = 60000;
285 uint32_t const dataPointsPerMinute = oneMinuteInMs / timestampInterval_ms;
286 uint32_t rollbackBytes = dataPointsPerMinute * recordSize;
287
288 // 3) Clamp rollbackBytes to something reasonable. We must not exceed
289 // the usable flash region (from address=1 to address=flash->size()-1).
290 uint32_t const maxUsable = flash->size() - DATA_START_ADDRESS;
291 if (rollbackBytes > maxUsable) {
292 // If we can't keep an entire minute, just keep as much as we can
293 rollbackBytes = maxUsable;
294 }
295
296 // 4) Next, we do ring-buffer math to find our new launchWriteAddress
297 // which is "1 minute's worth of data behind nextWriteAddress" *in a circular sense*.
298
299 // Because nextWriteAddress can be anywhere in [1, flash->size()-1],
300 // let’s do a safe modular subtraction:
301 //
302 // newAddr = ( nextWriteAddress + flash->size() - rollbackBytes )
303 // % flash->size()
304 //
305 // Then we ensure it’s never 0 because 0 is used for metadata.
306
307 uint32_t const sizeOfFlash = flash->size();
308
309 // Make sure we aren't in the metadata region
310 if (nextWriteAddress < DATA_START_ADDRESS) {
311 nextWriteAddress = DATA_START_ADDRESS;
312 }
313
314 // Use 64-bit to avoid any negative wrap during the subtraction.
315 int64_t potentialAddr = static_cast<int64_t>(nextWriteAddress)
316 + static_cast<int64_t>(sizeOfFlash) // ensure positivity
317 - static_cast<int64_t>(rollbackBytes);
318
319 // Modulo by sizeOfFlash to bring it back into [0, sizeOfFlash-1].
320 potentialAddr = potentialAddr % sizeOfFlash;
321
322 // If result is 0 or negative after mod, or less than DATA_START_ADDRESS, add sizeOfFlash
323 if (potentialAddr <= 0 || potentialAddr < DATA_START_ADDRESS) {
324 potentialAddr += sizeOfFlash;
325 }
326
327
328 launchWriteAddress = static_cast<uint32_t>(potentialAddr);
329
330 std::array<uint8_t, sizeof(launchWriteAddress)> bytes;
331 std::memcpy(bytes.data(), &launchWriteAddress, sizeof(launchWriteAddress));
332 flash->writeBuffer(LAUNCH_START_ADDRESS_ADDRESS, bytes.data(), bytes.size());
333
334}
335
336bool DataSaverSPI::writeToFlash(const uint8_t* data, size_t length) {
337 if (!flash->writeBuffer(nextWriteAddress, data, length)) {
338 return false;
339 }
340 nextWriteAddress += length;
341 return true;
342}
343
344bool DataSaverSPI::readFromFlash(uint32_t& readAddress, uint8_t* buffer, size_t length) {
345 if (!flash->readBuffer(readAddress, buffer, length)) {
346 return false;
347 }
348 readAddress += length;
349 return true;
350}
#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