blob: d64f1c2107a8fb979fcacbd9f9fa3ae85d3f431e [file] [log] [blame]
Brian Silvermanf7f267a2017-02-04 16:16:08 -08001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2016-2017. All Rights Reserved. */
3/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in the root directory of */
5/* the project. */
6/*----------------------------------------------------------------------------*/
7
8#include "HAL/SPI.h"
9
10#include <atomic>
11#include <cstdio>
12
13#include "DigitalInternal.h"
14#include "HAL/DIO.h"
15#include "HAL/HAL.h"
16#include "HAL/Notifier.h"
17#include "HAL/cpp/make_unique.h"
18#include "HAL/cpp/priority_mutex.h"
19#include "HAL/handles/HandlesInternal.h"
20#include "spilib/spi-lib.h"
21
22using namespace hal;
23
24static int32_t m_spiCS0Handle = 0;
25static int32_t m_spiCS1Handle = 0;
26static int32_t m_spiCS2Handle = 0;
27static int32_t m_spiCS3Handle = 0;
28static int32_t m_spiMXPHandle = 0;
29static priority_recursive_mutex spiOnboardMutex;
30static priority_recursive_mutex spiMXPMutex;
31
32// MXP SPI does not count towards this
33std::atomic<int32_t> spiPortCount{0};
34
35static HAL_DigitalHandle digitalHandles[9]{HAL_kInvalidHandle};
36
37/**
38 * Get the semaphore for a SPI port
39 *
40 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
41 * @return The semaphore for the SPI port.
42 */
43static priority_recursive_mutex& spiGetMutex(int32_t port) {
44 if (port < 4)
45 return spiOnboardMutex;
46 else
47 return spiMXPMutex;
48}
49
50extern "C" {
51
52struct SPIAccumulator {
53 std::atomic<HAL_NotifierHandle> notifier{0};
54 uint64_t triggerTime;
55 int32_t period;
56
57 int64_t value = 0;
58 uint32_t count = 0;
59 int32_t lastValue = 0;
60
61 int32_t center = 0;
62 int32_t deadband = 0;
63
64 uint8_t cmd[4]; // command to send (up to 4 bytes)
65 int32_t validMask;
66 int32_t validValue;
67 int32_t dataMax; // one more than max data value
68 int32_t dataMsbMask; // data field MSB mask (for signed)
69 uint8_t dataShift; // data field shift right amount, in bits
70 uint8_t xferSize; // SPI transfer size, in bytes (up to 4)
71 uint8_t port;
72 bool isSigned; // is data field signed?
73 bool bigEndian; // is response big endian?
74};
75std::unique_ptr<SPIAccumulator> spiAccumulators[5];
76
77static void CommonSPIPortInit(int32_t* status) {
78 // All false cases will set
79 if (spiPortCount.fetch_add(1) == 0) {
80 // Have not been initialized yet
81 initializeDigital(status);
82 if (*status != 0) return;
83 // MISO
84 if ((digitalHandles[3] = HAL_InitializeDIOPort(createPortHandleForSPI(29),
85 false, status)) ==
86 HAL_kInvalidHandle) {
87 std::printf("Failed to allocate DIO 29 (MISO)\n");
88 return;
89 }
90 // MOSI
91 if ((digitalHandles[4] = HAL_InitializeDIOPort(createPortHandleForSPI(30),
92 false, status)) ==
93 HAL_kInvalidHandle) {
94 std::printf("Failed to allocate DIO 30 (MOSI)\n");
95 HAL_FreeDIOPort(digitalHandles[3]); // free the first port allocated
96 return;
97 }
98 }
99}
100
101static void CommonSPIPortFree() {
102 if (spiPortCount.fetch_sub(1) == 1) {
103 // Clean up SPI Handles
104 HAL_FreeDIOPort(digitalHandles[3]);
105 HAL_FreeDIOPort(digitalHandles[4]);
106 }
107}
108
109/*
110 * Initialize the spi port. Opens the port if necessary and saves the handle.
111 * If opening the MXP port, also sets up the channel functions appropriately
112 * @param port The number of the port to use. 0-3 for Onboard CS0-CS3, 4 for MXP
113 */
114void HAL_InitializeSPI(int32_t port, int32_t* status) {
115 if (HAL_GetSPIHandle(port) != 0) return;
116 switch (port) {
117 case 0:
118 CommonSPIPortInit(status);
119 if (*status != 0) return;
120 // CS0 is not a DIO port, so nothing to allocate
121 HAL_SetSPIHandle(0, spilib_open("/dev/spidev0.0"));
122 break;
123 case 1:
124 CommonSPIPortInit(status);
125 if (*status != 0) return;
126 // CS1, Allocate
127 if ((digitalHandles[0] = HAL_InitializeDIOPort(
128 HAL_GetPort(26), false, status)) == HAL_kInvalidHandle) {
129 std::printf("Failed to allocate DIO 26 (CS1)\n");
130 CommonSPIPortFree();
131 return;
132 }
133 HAL_SetSPIHandle(1, spilib_open("/dev/spidev0.1"));
134 break;
135 case 2:
136 CommonSPIPortInit(status);
137 if (*status != 0) return;
138 // CS2, Allocate
139 if ((digitalHandles[1] = HAL_InitializeDIOPort(
140 HAL_GetPort(27), false, status)) == HAL_kInvalidHandle) {
141 std::printf("Failed to allocate DIO 27 (CS2)\n");
142 CommonSPIPortFree();
143 return;
144 }
145 HAL_SetSPIHandle(2, spilib_open("/dev/spidev0.2"));
146 break;
147 case 3:
148 CommonSPIPortInit(status);
149 if (*status != 0) return;
150 // CS3, Allocate
151 if ((digitalHandles[2] = HAL_InitializeDIOPort(
152 HAL_GetPort(28), false, status)) == HAL_kInvalidHandle) {
153 std::printf("Failed to allocate DIO 28 (CS3)\n");
154 CommonSPIPortFree();
155 return;
156 }
157 HAL_SetSPIHandle(3, spilib_open("/dev/spidev0.3"));
158 break;
159 case 4:
160 initializeDigital(status);
161 if (*status != 0) return;
162 if ((digitalHandles[5] = HAL_InitializeDIOPort(
163 HAL_GetPort(14), false, status)) == HAL_kInvalidHandle) {
164 std::printf("Failed to allocate DIO 14\n");
165 return;
166 }
167 if ((digitalHandles[6] = HAL_InitializeDIOPort(
168 HAL_GetPort(15), false, status)) == HAL_kInvalidHandle) {
169 std::printf("Failed to allocate DIO 15\n");
170 HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated
171 return;
172 }
173 if ((digitalHandles[7] = HAL_InitializeDIOPort(
174 HAL_GetPort(16), false, status)) == HAL_kInvalidHandle) {
175 std::printf("Failed to allocate DIO 16\n");
176 HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated
177 HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated
178 return;
179 }
180 if ((digitalHandles[8] = HAL_InitializeDIOPort(
181 HAL_GetPort(17), false, status)) == HAL_kInvalidHandle) {
182 std::printf("Failed to allocate DIO 17\n");
183 HAL_FreeDIOPort(digitalHandles[5]); // free the first port allocated
184 HAL_FreeDIOPort(digitalHandles[6]); // free the second port allocated
185 HAL_FreeDIOPort(digitalHandles[7]); // free the third port allocated
186 return;
187 }
188 digitalSystem->writeEnableMXPSpecialFunction(
189 digitalSystem->readEnableMXPSpecialFunction(status) | 0x00F0, status);
190 HAL_SetSPIHandle(4, spilib_open("/dev/spidev1.0"));
191 break;
192 default:
193 *status = PARAMETER_OUT_OF_RANGE;
194 break;
195 }
196 return;
197}
198
199/**
200 * Generic transaction.
201 *
202 * This is a lower-level interface to the spi hardware giving you more control
203 * over each transaction.
204 *
205 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
206 * @param dataToSend Buffer of data to send as part of the transaction.
207 * @param dataReceived Buffer to read data into.
208 * @param size Number of bytes to transfer. [0..7]
209 * @return Number of bytes transferred, -1 for error
210 */
211int32_t HAL_TransactionSPI(int32_t port, uint8_t* dataToSend,
212 uint8_t* dataReceived, int32_t size) {
213 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
214 return spilib_writeread(
215 HAL_GetSPIHandle(port), reinterpret_cast<const char*>(dataToSend),
216 reinterpret_cast<char*>(dataReceived), static_cast<int32_t>(size));
217}
218
219/**
220 * Execute a write transaction with the device.
221 *
222 * Write to a device and wait until the transaction is complete.
223 *
224 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
225 * @param datToSend The data to write to the register on the device.
226 * @param sendSize The number of bytes to be written
227 * @return The number of bytes written. -1 for an error
228 */
229int32_t HAL_WriteSPI(int32_t port, uint8_t* dataToSend, int32_t sendSize) {
230 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
231 return spilib_write(HAL_GetSPIHandle(port),
232 reinterpret_cast<const char*>(dataToSend),
233 static_cast<int32_t>(sendSize));
234}
235
236/**
237 * Execute a read from the device.
238 *
239 * This method does not write any data out to the device
240 * Most spi devices will require a register address to be written before
241 * they begin returning data
242 *
243 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
244 * @param buffer A pointer to the array of bytes to store the data read from the
245 * device.
246 * @param count The number of bytes to read in the transaction. [1..7]
247 * @return Number of bytes read. -1 for error.
248 */
249int32_t HAL_ReadSPI(int32_t port, uint8_t* buffer, int32_t count) {
250 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
251 return spilib_read(HAL_GetSPIHandle(port), reinterpret_cast<char*>(buffer),
252 static_cast<int32_t>(count));
253}
254
255/**
256 * Close the SPI port
257 *
258 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
259 */
260void HAL_CloseSPI(int32_t port) {
261 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
262 if (spiAccumulators[port]) {
263 int32_t status = 0;
264 HAL_FreeSPIAccumulator(port, &status);
265 }
266 spilib_close(HAL_GetSPIHandle(port));
267 HAL_SetSPIHandle(port, 0);
268 if (port < 4) {
269 CommonSPIPortFree();
270 }
271 switch (port) {
272 // Case 0 does not need to do anything
273 case 1:
274 HAL_FreeDIOPort(digitalHandles[0]);
275 break;
276 case 2:
277 HAL_FreeDIOPort(digitalHandles[1]);
278 break;
279 case 3:
280 HAL_FreeDIOPort(digitalHandles[2]);
281 break;
282 case 4:
283 HAL_FreeDIOPort(digitalHandles[5]);
284 HAL_FreeDIOPort(digitalHandles[6]);
285 HAL_FreeDIOPort(digitalHandles[7]);
286 HAL_FreeDIOPort(digitalHandles[8]);
287 break;
288 default:
289 break;
290 }
291 return;
292}
293
294/**
295 * Set the clock speed for the SPI bus.
296 *
297 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
298 * @param speed The speed in Hz (0-1MHz)
299 */
300void HAL_SetSPISpeed(int32_t port, int32_t speed) {
301 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
302 spilib_setspeed(HAL_GetSPIHandle(port), speed);
303}
304
305/**
306 * Set the SPI options
307 *
308 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
309 * @param msbFirst True to write the MSB first, False for LSB first
310 * @param sampleOnTrailing True to sample on the trailing edge, False to sample
311 * on the leading edge
312 * @param clkIdleHigh True to set the clock to active low, False to set the
313 * clock active high
314 */
315void HAL_SetSPIOpts(int32_t port, HAL_Bool msbFirst, HAL_Bool sampleOnTrailing,
316 HAL_Bool clkIdleHigh) {
317 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
318 spilib_setopts(HAL_GetSPIHandle(port), msbFirst, sampleOnTrailing,
319 clkIdleHigh);
320}
321
322/**
323 * Set the CS Active high for a SPI port
324 *
325 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
326 */
327void HAL_SetSPIChipSelectActiveHigh(int32_t port, int32_t* status) {
328 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
329 if (port < 4) {
330 spiSystem->writeChipSelectActiveHigh_Hdr(
331 spiSystem->readChipSelectActiveHigh_Hdr(status) | (1 << port), status);
332 } else {
333 spiSystem->writeChipSelectActiveHigh_MXP(1, status);
334 }
335}
336
337/**
338 * Set the CS Active low for a SPI port
339 *
340 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
341 */
342void HAL_SetSPIChipSelectActiveLow(int32_t port, int32_t* status) {
343 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
344 if (port < 4) {
345 spiSystem->writeChipSelectActiveHigh_Hdr(
346 spiSystem->readChipSelectActiveHigh_Hdr(status) & ~(1 << port), status);
347 } else {
348 spiSystem->writeChipSelectActiveHigh_MXP(0, status);
349 }
350}
351
352/**
353 * Get the stored handle for a SPI port
354 *
355 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for MXP
356 * @return The stored handle for the SPI port. 0 represents no stored handle.
357 */
358int32_t HAL_GetSPIHandle(int32_t port) {
359 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
360 switch (port) {
361 case 0:
362 return m_spiCS0Handle;
363 case 1:
364 return m_spiCS1Handle;
365 case 2:
366 return m_spiCS2Handle;
367 case 3:
368 return m_spiCS3Handle;
369 case 4:
370 return m_spiMXPHandle;
371 default:
372 return 0;
373 }
374}
375
376/**
377 * Set the stored handle for a SPI port
378 *
379 * @param port The number of the port to use. 0-3 for Onboard CS0-CS2, 4 for
380 * MXP.
381 * @param handle The value of the handle for the port.
382 */
383void HAL_SetSPIHandle(int32_t port, int32_t handle) {
384 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
385 switch (port) {
386 case 0:
387 m_spiCS0Handle = handle;
388 break;
389 case 1:
390 m_spiCS1Handle = handle;
391 break;
392 case 2:
393 m_spiCS2Handle = handle;
394 break;
395 case 3:
396 m_spiCS3Handle = handle;
397 break;
398 case 4:
399 m_spiMXPHandle = handle;
400 break;
401 default:
402 break;
403 }
404}
405
406static void spiAccumulatorProcess(uint64_t currentTime,
407 HAL_NotifierHandle handle) {
408 int32_t status = 0;
409 auto param = HAL_GetNotifierParam(handle, &status);
410 if (param == nullptr) return;
411 SPIAccumulator* accum = static_cast<SPIAccumulator*>(param);
412
413 // perform SPI transaction
414 uint8_t resp_b[4];
415 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(accum->port));
416 spilib_writeread(
417 HAL_GetSPIHandle(accum->port), reinterpret_cast<const char*>(accum->cmd),
418 reinterpret_cast<char*>(resp_b), static_cast<int32_t>(accum->xferSize));
419
420 // convert from bytes
421 uint32_t resp = 0;
422 if (accum->bigEndian) {
423 for (int32_t i = 0; i < accum->xferSize; ++i) {
424 resp <<= 8;
425 resp |= resp_b[i] & 0xff;
426 }
427 } else {
428 for (int32_t i = accum->xferSize - 1; i >= 0; --i) {
429 resp <<= 8;
430 resp |= resp_b[i] & 0xff;
431 }
432 }
433
434 // process response
435 if ((resp & accum->validMask) == static_cast<uint32_t>(accum->validValue)) {
436 // valid sensor data; extract data field
437 int32_t data = static_cast<int32_t>(resp >> accum->dataShift);
438 data &= accum->dataMax - 1;
439 // 2s complement conversion if signed MSB is set
440 if (accum->isSigned && (data & accum->dataMsbMask) != 0)
441 data -= accum->dataMax;
442 // center offset
443 data -= accum->center;
444 // only accumulate if outside deadband
445 if (data < -accum->deadband || data > accum->deadband) accum->value += data;
446 ++accum->count;
447 accum->lastValue = data;
448 } else {
449 // no data from the sensor; just clear the last value
450 accum->lastValue = 0;
451 }
452
453 // reschedule timer
454 accum->triggerTime += accum->period;
455 // handle timer slip
456 if (accum->triggerTime < currentTime)
457 accum->triggerTime = currentTime + accum->period;
458 status = 0;
459 HAL_UpdateNotifierAlarm(accum->notifier, accum->triggerTime, &status);
460}
461
462/**
463 * Initialize a SPI accumulator.
464 *
465 * @param port SPI port
466 * @param period Time between reads, in us
467 * @param cmd SPI command to send to request data
468 * @param xferSize SPI transfer size, in bytes
469 * @param validMask Mask to apply to received data for validity checking
470 * @param valid_data After validMask is applied, required matching value for
471 * validity checking
472 * @param dataShift Bit shift to apply to received data to get actual data
473 * value
474 * @param dataSize Size (in bits) of data field
475 * @param isSigned Is data field signed?
476 * @param bigEndian Is device big endian?
477 */
478void HAL_InitSPIAccumulator(int32_t port, int32_t period, int32_t cmd,
479 int32_t xferSize, int32_t validMask,
480 int32_t validValue, int32_t dataShift,
481 int32_t dataSize, HAL_Bool isSigned,
482 HAL_Bool bigEndian, int32_t* status) {
483 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
484 if (port > 4) return;
485 if (!spiAccumulators[port])
486 spiAccumulators[port] = std::make_unique<SPIAccumulator>();
487 SPIAccumulator* accum = spiAccumulators[port].get();
488 if (bigEndian) {
489 for (int32_t i = xferSize - 1; i >= 0; --i) {
490 accum->cmd[i] = cmd & 0xff;
491 cmd >>= 8;
492 }
493 } else {
494 accum->cmd[0] = cmd & 0xff;
495 cmd >>= 8;
496 accum->cmd[1] = cmd & 0xff;
497 cmd >>= 8;
498 accum->cmd[2] = cmd & 0xff;
499 cmd >>= 8;
500 accum->cmd[3] = cmd & 0xff;
501 }
502 accum->period = period;
503 accum->xferSize = xferSize;
504 accum->validMask = validMask;
505 accum->validValue = validValue;
506 accum->dataShift = dataShift;
507 accum->dataMax = (1 << dataSize);
508 accum->dataMsbMask = (1 << (dataSize - 1));
509 accum->isSigned = isSigned;
510 accum->bigEndian = bigEndian;
511 if (!accum->notifier) {
512 accum->notifier =
513 HAL_InitializeNotifier(spiAccumulatorProcess, accum, status);
514 accum->triggerTime = HAL_GetFPGATime(status) + period;
515 if (*status != 0) return;
516 HAL_UpdateNotifierAlarm(accum->notifier, accum->triggerTime, status);
517 }
518}
519
520/**
521 * Frees a SPI accumulator.
522 */
523void HAL_FreeSPIAccumulator(int32_t port, int32_t* status) {
524 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
525 SPIAccumulator* accum = spiAccumulators[port].get();
526 if (!accum) {
527 *status = NULL_PARAMETER;
528 return;
529 }
530 HAL_NotifierHandle handle = accum->notifier.exchange(0);
531 HAL_CleanNotifier(handle, status);
532 spiAccumulators[port] = nullptr;
533}
534
535/**
536 * Resets the accumulator to zero.
537 */
538void HAL_ResetSPIAccumulator(int32_t port, int32_t* status) {
539 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
540 SPIAccumulator* accum = spiAccumulators[port].get();
541 if (!accum) {
542 *status = NULL_PARAMETER;
543 return;
544 }
545 accum->value = 0;
546 accum->count = 0;
547 accum->lastValue = 0;
548}
549
550/**
551 * Set the center value of the accumulator.
552 *
553 * The center value is subtracted from each value before it is added to the
554 * accumulator. This
555 * is used for the center value of devices like gyros and accelerometers to make
556 * integration work
557 * and to take the device offset into account when integrating.
558 */
559void HAL_SetSPIAccumulatorCenter(int32_t port, int32_t center,
560 int32_t* status) {
561 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
562 SPIAccumulator* accum = spiAccumulators[port].get();
563 if (!accum) {
564 *status = NULL_PARAMETER;
565 return;
566 }
567 accum->center = center;
568}
569
570/**
571 * Set the accumulator's deadband.
572 */
573void HAL_SetSPIAccumulatorDeadband(int32_t port, int32_t deadband,
574 int32_t* status) {
575 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
576 SPIAccumulator* accum = spiAccumulators[port].get();
577 if (!accum) {
578 *status = NULL_PARAMETER;
579 return;
580 }
581 accum->deadband = deadband;
582}
583
584/**
585 * Read the last value read by the accumulator engine.
586 */
587int32_t HAL_GetSPIAccumulatorLastValue(int32_t port, int32_t* status) {
588 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
589 SPIAccumulator* accum = spiAccumulators[port].get();
590 if (!accum) {
591 *status = NULL_PARAMETER;
592 return 0;
593 }
594 return accum->lastValue;
595}
596
597/**
598 * Read the accumulated value.
599 *
600 * @return The 64-bit value accumulated since the last Reset().
601 */
602int64_t HAL_GetSPIAccumulatorValue(int32_t port, int32_t* status) {
603 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
604 SPIAccumulator* accum = spiAccumulators[port].get();
605 if (!accum) {
606 *status = NULL_PARAMETER;
607 return 0;
608 }
609 return accum->value;
610}
611
612/**
613 * Read the number of accumulated values.
614 *
615 * Read the count of the accumulated values since the accumulator was last
616 * Reset().
617 *
618 * @return The number of times samples from the channel were accumulated.
619 */
620int64_t HAL_GetSPIAccumulatorCount(int32_t port, int32_t* status) {
621 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
622 SPIAccumulator* accum = spiAccumulators[port].get();
623 if (!accum) {
624 *status = NULL_PARAMETER;
625 return 0;
626 }
627 return accum->count;
628}
629
630/**
631 * Read the average of the accumulated value.
632 *
633 * @return The accumulated average value (value / count).
634 */
635double HAL_GetSPIAccumulatorAverage(int32_t port, int32_t* status) {
636 int64_t value;
637 int64_t count;
638 HAL_GetSPIAccumulatorOutput(port, &value, &count, status);
639 if (count == 0) return 0.0;
640 return static_cast<double>(value) / count;
641}
642
643/**
644 * Read the accumulated value and the number of accumulated values atomically.
645 *
646 * This function reads the value and count atomically.
647 * This can be used for averaging.
648 *
649 * @param value Pointer to the 64-bit accumulated output.
650 * @param count Pointer to the number of accumulation cycles.
651 */
652void HAL_GetSPIAccumulatorOutput(int32_t port, int64_t* value, int64_t* count,
653 int32_t* status) {
654 std::lock_guard<priority_recursive_mutex> sync(spiGetMutex(port));
655 SPIAccumulator* accum = spiAccumulators[port].get();
656 if (!accum) {
657 *status = NULL_PARAMETER;
658 *value = 0;
659 *count = 0;
660 return;
661 }
662 *value = accum->value;
663 *count = accum->count;
664}
665}