blob: 29cd00635e5dac388f769a7961d8b7de45b540fc [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
Brian Silverman8fce7482020-01-05 13:18:21 -08004
5#include "frc/SPI.h"
6
7#include <cstring>
Austin Schuh812d0d12021-11-04 20:16:48 -07008#include <memory>
Brian Silverman8fce7482020-01-05 13:18:21 -08009#include <utility>
10
11#include <hal/FRCUsageReporting.h>
12#include <hal/SPI.h>
13#include <wpi/SmallVector.h>
14#include <wpi/mutex.h>
15
16#include "frc/DigitalSource.h"
Austin Schuh812d0d12021-11-04 20:16:48 -070017#include "frc/Errors.h"
Brian Silverman8fce7482020-01-05 13:18:21 -080018#include "frc/Notifier.h"
Brian Silverman8fce7482020-01-05 13:18:21 -080019
20using namespace frc;
21
22static constexpr int kAccumulateDepth = 2048;
23
24class SPI::Accumulator {
25 public:
26 Accumulator(HAL_SPIPort port, int xferSize, int validMask, int validValue,
27 int dataShift, int dataSize, bool isSigned, bool bigEndian)
James Kuszmaulcf324122023-01-14 14:07:17 -080028 : m_notifier([=, this] {
Brian Silverman8fce7482020-01-05 13:18:21 -080029 std::scoped_lock lock(m_mutex);
30 Update();
31 }),
32 m_buf(new uint32_t[(xferSize + 1) * kAccumulateDepth]),
33 m_validMask(validMask),
34 m_validValue(validValue),
35 m_dataMax(1 << dataSize),
36 m_dataMsbMask(1 << (dataSize - 1)),
37 m_dataShift(dataShift),
38 m_xferSize(xferSize + 1), // +1 for timestamp
39 m_isSigned(isSigned),
40 m_bigEndian(bigEndian),
41 m_port(port) {}
42 ~Accumulator() { delete[] m_buf; }
43
44 void Update();
45
46 Notifier m_notifier;
47 uint32_t* m_buf;
48 wpi::mutex m_mutex;
49
50 int64_t m_value = 0;
51 uint32_t m_count = 0;
52 int32_t m_lastValue = 0;
53 uint32_t m_lastTimestamp = 0;
54 double m_integratedValue = 0;
55
56 int32_t m_center = 0;
57 int32_t m_deadband = 0;
58 double m_integratedCenter = 0;
59
60 int32_t m_validMask;
61 int32_t m_validValue;
62 int32_t m_dataMax; // one more than max data value
63 int32_t m_dataMsbMask; // data field MSB mask (for signed)
64 uint8_t m_dataShift; // data field shift right amount, in bits
65 int32_t m_xferSize; // SPI transfer size, in bytes
66 bool m_isSigned; // is data field signed?
67 bool m_bigEndian; // is response big endian?
68 HAL_SPIPort m_port;
69};
70
71void SPI::Accumulator::Update() {
72 bool done;
73 do {
74 done = true;
75 int32_t status = 0;
76
77 // get amount of data available
78 int32_t numToRead =
79 HAL_ReadSPIAutoReceivedData(m_port, m_buf, 0, 0, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -080080 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -080081
82 // only get whole responses; +1 is for timestamp
83 numToRead -= numToRead % m_xferSize;
84 if (numToRead > m_xferSize * kAccumulateDepth) {
85 numToRead = m_xferSize * kAccumulateDepth;
86 done = false;
87 }
Austin Schuh812d0d12021-11-04 20:16:48 -070088 if (numToRead == 0) {
89 return; // no samples
90 }
Brian Silverman8fce7482020-01-05 13:18:21 -080091
92 // read buffered data
93 HAL_ReadSPIAutoReceivedData(m_port, m_buf, numToRead, 0, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -080094 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -080095
96 // loop over all responses
97 for (int32_t off = 0; off < numToRead; off += m_xferSize) {
98 // get timestamp from first word
99 uint32_t timestamp = m_buf[off];
100
101 // convert from bytes
102 uint32_t resp = 0;
103 if (m_bigEndian) {
104 for (int32_t i = 1; i < m_xferSize; ++i) {
105 resp <<= 8;
106 resp |= m_buf[off + i] & 0xff;
107 }
108 } else {
109 for (int32_t i = m_xferSize - 1; i >= 1; --i) {
110 resp <<= 8;
111 resp |= m_buf[off + i] & 0xff;
112 }
113 }
114
115 // process response
116 if ((resp & m_validMask) == static_cast<uint32_t>(m_validValue)) {
117 // valid sensor data; extract data field
118 int32_t data = static_cast<int32_t>(resp >> m_dataShift);
119 data &= m_dataMax - 1;
120 // 2s complement conversion if signed MSB is set
Austin Schuh812d0d12021-11-04 20:16:48 -0700121 if (m_isSigned && (data & m_dataMsbMask) != 0) {
122 data -= m_dataMax;
123 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800124 // center offset
125 int32_t dataNoCenter = data;
126 data -= m_center;
127 // only accumulate if outside deadband
128 if (data < -m_deadband || data > m_deadband) {
129 m_value += data;
130 if (m_count != 0) {
131 // timestamps use the 1us FPGA clock; also handle rollover
Austin Schuh812d0d12021-11-04 20:16:48 -0700132 if (timestamp >= m_lastTimestamp) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800133 m_integratedValue +=
134 dataNoCenter *
135 static_cast<int32_t>(timestamp - m_lastTimestamp) * 1e-6 -
136 m_integratedCenter;
Austin Schuh812d0d12021-11-04 20:16:48 -0700137 } else {
Brian Silverman8fce7482020-01-05 13:18:21 -0800138 m_integratedValue +=
139 dataNoCenter *
140 static_cast<int32_t>((1ULL << 32) - m_lastTimestamp +
141 timestamp) *
142 1e-6 -
143 m_integratedCenter;
Austin Schuh812d0d12021-11-04 20:16:48 -0700144 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800145 }
146 }
147 ++m_count;
148 m_lastValue = data;
149 } else {
150 // no data from the sensor; just clear the last value
151 m_lastValue = 0;
152 }
153 m_lastTimestamp = timestamp;
154 }
155 } while (!done);
156}
157
158SPI::SPI(Port port) : m_port(static_cast<HAL_SPIPort>(port)) {
159 int32_t status = 0;
160 HAL_InitializeSPI(m_port, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800161 HAL_SetSPIMode(m_port, m_mode);
162 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800163
164 HAL_Report(HALUsageReporting::kResourceType_SPI,
165 static_cast<uint8_t>(port) + 1);
166}
167
Austin Schuh812d0d12021-11-04 20:16:48 -0700168SPI::~SPI() {
169 HAL_CloseSPI(m_port);
170}
Brian Silverman8fce7482020-01-05 13:18:21 -0800171
Austin Schuh812d0d12021-11-04 20:16:48 -0700172SPI::Port SPI::GetPort() const {
173 return static_cast<Port>(static_cast<int>(m_port));
174}
175
176void SPI::SetClockRate(int hz) {
177 HAL_SetSPISpeed(m_port, hz);
178}
Brian Silverman8fce7482020-01-05 13:18:21 -0800179
180void SPI::SetMSBFirst() {
James Kuszmaulcf324122023-01-14 14:07:17 -0800181 FRC_ReportError(1, "SetMSBFirst not supported by roboRIO {}",
182 static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800183}
184
185void SPI::SetLSBFirst() {
James Kuszmaulcf324122023-01-14 14:07:17 -0800186 FRC_ReportError(1, "SetLSBFirst not supported by roboRIO {}",
187 static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800188}
189
190void SPI::SetSampleDataOnLeadingEdge() {
James Kuszmaulcf324122023-01-14 14:07:17 -0800191 int mode = m_mode;
192 mode &= 2;
193 m_mode = static_cast<HAL_SPIMode>(mode);
194 HAL_SetSPIMode(m_port, m_mode);
Brian Silverman8fce7482020-01-05 13:18:21 -0800195}
196
197void SPI::SetSampleDataOnTrailingEdge() {
James Kuszmaulcf324122023-01-14 14:07:17 -0800198 int mode = m_mode;
199 mode |= 2;
200 m_mode = static_cast<HAL_SPIMode>(mode);
201 HAL_SetSPIMode(m_port, m_mode);
Brian Silverman8fce7482020-01-05 13:18:21 -0800202}
203
204void SPI::SetClockActiveLow() {
James Kuszmaulcf324122023-01-14 14:07:17 -0800205 int mode = m_mode;
206 mode |= 1;
207 m_mode = static_cast<HAL_SPIMode>(mode);
208 HAL_SetSPIMode(m_port, m_mode);
Brian Silverman8fce7482020-01-05 13:18:21 -0800209}
210
211void SPI::SetClockActiveHigh() {
James Kuszmaulcf324122023-01-14 14:07:17 -0800212 int mode = m_mode;
213 mode &= 1;
214 m_mode = static_cast<HAL_SPIMode>(mode);
215 HAL_SetSPIMode(m_port, m_mode);
216}
217
218void SPI::SetMode(Mode mode) {
219 m_mode = static_cast<HAL_SPIMode>(mode & 0x3);
220 HAL_SetSPIMode(m_port, m_mode);
Brian Silverman8fce7482020-01-05 13:18:21 -0800221}
222
223void SPI::SetChipSelectActiveHigh() {
224 int32_t status = 0;
225 HAL_SetSPIChipSelectActiveHigh(m_port, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800226 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800227}
228
229void SPI::SetChipSelectActiveLow() {
230 int32_t status = 0;
231 HAL_SetSPIChipSelectActiveLow(m_port, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800232 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800233}
234
235int SPI::Write(uint8_t* data, int size) {
236 int retVal = 0;
237 retVal = HAL_WriteSPI(m_port, data, size);
238 return retVal;
239}
240
241int SPI::Read(bool initiate, uint8_t* dataReceived, int size) {
242 int retVal = 0;
243 if (initiate) {
244 wpi::SmallVector<uint8_t, 32> dataToSend;
245 dataToSend.resize(size);
246 retVal = HAL_TransactionSPI(m_port, dataToSend.data(), dataReceived, size);
247 } else {
248 retVal = HAL_ReadSPI(m_port, dataReceived, size);
249 }
250 return retVal;
251}
252
253int SPI::Transaction(uint8_t* dataToSend, uint8_t* dataReceived, int size) {
254 int retVal = 0;
255 retVal = HAL_TransactionSPI(m_port, dataToSend, dataReceived, size);
256 return retVal;
257}
258
259void SPI::InitAuto(int bufferSize) {
260 int32_t status = 0;
261 HAL_InitSPIAuto(m_port, bufferSize, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800262 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800263}
264
265void SPI::FreeAuto() {
266 int32_t status = 0;
267 HAL_FreeSPIAuto(m_port, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800268 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800269}
270
James Kuszmaulcf324122023-01-14 14:07:17 -0800271void SPI::SetAutoTransmitData(std::span<const uint8_t> dataToSend,
Austin Schuh812d0d12021-11-04 20:16:48 -0700272 int zeroSize) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800273 int32_t status = 0;
274 HAL_SetSPIAutoTransmitData(m_port, dataToSend.data(), dataToSend.size(),
275 zeroSize, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800276 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800277}
278
279void SPI::StartAutoRate(units::second_t period) {
280 int32_t status = 0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700281 HAL_StartSPIAutoRate(m_port, period.value(), &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800282 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800283}
284
285void SPI::StartAutoTrigger(DigitalSource& source, bool rising, bool falling) {
286 int32_t status = 0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700287 HAL_StartSPIAutoTrigger(m_port, source.GetPortHandleForRouting(),
288 static_cast<HAL_AnalogTriggerType>(
289 source.GetAnalogTriggerTypeForRouting()),
290 rising, falling, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800291 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800292}
293
294void SPI::StopAuto() {
295 int32_t status = 0;
296 HAL_StopSPIAuto(m_port, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800297 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800298}
299
300void SPI::ForceAutoRead() {
301 int32_t status = 0;
302 HAL_ForceSPIAutoRead(m_port, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800303 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800304}
305
306int SPI::ReadAutoReceivedData(uint32_t* buffer, int numToRead,
307 units::second_t timeout) {
308 int32_t status = 0;
309 int32_t val = HAL_ReadSPIAutoReceivedData(m_port, buffer, numToRead,
Austin Schuh812d0d12021-11-04 20:16:48 -0700310 timeout.value(), &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800311 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800312 return val;
313}
314
Brian Silverman8fce7482020-01-05 13:18:21 -0800315int SPI::GetAutoDroppedCount() {
316 int32_t status = 0;
317 int32_t val = HAL_GetSPIAutoDroppedCount(m_port, &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800318 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800319 return val;
320}
321
322void SPI::ConfigureAutoStall(HAL_SPIPort port, int csToSclkTicks,
323 int stallTicks, int pow2BytesPerRead) {
324 int32_t status = 0;
325 HAL_ConfigureSPIAutoStall(m_port, csToSclkTicks, stallTicks, pow2BytesPerRead,
326 &status);
James Kuszmaulcf324122023-01-14 14:07:17 -0800327 FRC_CheckErrorStatus(status, "Port {}", static_cast<int>(m_port));
Brian Silverman8fce7482020-01-05 13:18:21 -0800328}
329
330void SPI::InitAccumulator(units::second_t period, int cmd, int xferSize,
331 int validMask, int validValue, int dataShift,
332 int dataSize, bool isSigned, bool bigEndian) {
333 InitAuto(xferSize * kAccumulateDepth);
334 uint8_t cmdBytes[4] = {0, 0, 0, 0};
335 if (bigEndian) {
336 for (int32_t i = xferSize - 1; i >= 0; --i) {
337 cmdBytes[i] = cmd & 0xff;
338 cmd >>= 8;
339 }
340 } else {
341 cmdBytes[0] = cmd & 0xff;
342 cmd >>= 8;
343 cmdBytes[1] = cmd & 0xff;
344 cmd >>= 8;
345 cmdBytes[2] = cmd & 0xff;
346 cmd >>= 8;
347 cmdBytes[3] = cmd & 0xff;
348 }
349 SetAutoTransmitData(cmdBytes, xferSize - 4);
350 StartAutoRate(period);
351
Austin Schuh812d0d12021-11-04 20:16:48 -0700352 m_accum =
353 std::make_unique<Accumulator>(m_port, xferSize, validMask, validValue,
354 dataShift, dataSize, isSigned, bigEndian);
Brian Silverman8fce7482020-01-05 13:18:21 -0800355 m_accum->m_notifier.StartPeriodic(period * kAccumulateDepth / 2);
356}
357
Brian Silverman8fce7482020-01-05 13:18:21 -0800358void SPI::FreeAccumulator() {
359 m_accum.reset(nullptr);
360 FreeAuto();
361}
362
363void SPI::ResetAccumulator() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700364 if (!m_accum) {
365 return;
366 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800367 std::scoped_lock lock(m_accum->m_mutex);
368 m_accum->m_value = 0;
369 m_accum->m_count = 0;
370 m_accum->m_lastValue = 0;
371 m_accum->m_lastTimestamp = 0;
372 m_accum->m_integratedValue = 0;
373}
374
375void SPI::SetAccumulatorCenter(int center) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700376 if (!m_accum) {
377 return;
378 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800379 std::scoped_lock lock(m_accum->m_mutex);
380 m_accum->m_center = center;
381}
382
383void SPI::SetAccumulatorDeadband(int deadband) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700384 if (!m_accum) {
385 return;
386 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800387 std::scoped_lock lock(m_accum->m_mutex);
388 m_accum->m_deadband = deadband;
389}
390
391int SPI::GetAccumulatorLastValue() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700392 if (!m_accum) {
393 return 0;
394 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800395 std::scoped_lock lock(m_accum->m_mutex);
396 m_accum->Update();
397 return m_accum->m_lastValue;
398}
399
400int64_t SPI::GetAccumulatorValue() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700401 if (!m_accum) {
402 return 0;
403 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800404 std::scoped_lock lock(m_accum->m_mutex);
405 m_accum->Update();
406 return m_accum->m_value;
407}
408
409int64_t SPI::GetAccumulatorCount() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700410 if (!m_accum) {
411 return 0;
412 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800413 std::scoped_lock lock(m_accum->m_mutex);
414 m_accum->Update();
415 return m_accum->m_count;
416}
417
418double SPI::GetAccumulatorAverage() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700419 if (!m_accum) {
420 return 0;
421 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800422 std::scoped_lock lock(m_accum->m_mutex);
423 m_accum->Update();
Austin Schuh812d0d12021-11-04 20:16:48 -0700424 if (m_accum->m_count == 0) {
425 return 0.0;
426 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800427 return static_cast<double>(m_accum->m_value) / m_accum->m_count;
428}
429
430void SPI::GetAccumulatorOutput(int64_t& value, int64_t& count) const {
431 if (!m_accum) {
432 value = 0;
433 count = 0;
434 return;
435 }
436 std::scoped_lock lock(m_accum->m_mutex);
437 m_accum->Update();
438 value = m_accum->m_value;
439 count = m_accum->m_count;
440}
441
442void SPI::SetAccumulatorIntegratedCenter(double center) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700443 if (!m_accum) {
444 return;
445 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800446 std::scoped_lock lock(m_accum->m_mutex);
447 m_accum->m_integratedCenter = center;
448}
449
450double SPI::GetAccumulatorIntegratedValue() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700451 if (!m_accum) {
452 return 0;
453 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800454 std::scoped_lock lock(m_accum->m_mutex);
455 m_accum->Update();
456 return m_accum->m_integratedValue;
457}
458
459double SPI::GetAccumulatorIntegratedAverage() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700460 if (!m_accum) {
461 return 0;
462 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800463 std::scoped_lock lock(m_accum->m_mutex);
464 m_accum->Update();
Austin Schuh812d0d12021-11-04 20:16:48 -0700465 if (m_accum->m_count <= 1) {
466 return 0.0;
467 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800468 // count-1 due to not integrating the first value received
469 return m_accum->m_integratedValue / (m_accum->m_count - 1);
470}