blob: bdcee45eae82dfe21abdfafd9fcbb58d0b8e0bd6 [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)
Austin Schuh812d0d12021-11-04 20:16:48 -070028 : m_notifier([=] {
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);
Austin Schuh812d0d12021-11-04 20:16:48 -070080 FRC_CheckErrorStatus(status, "Port {}", 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);
Austin Schuh812d0d12021-11-04 20:16:48 -070094 FRC_CheckErrorStatus(status, "Port {}", 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);
Austin Schuh812d0d12021-11-04 20:16:48 -0700161 FRC_CheckErrorStatus(status, "Port {}", m_port);
Brian Silverman8fce7482020-01-05 13:18:21 -0800162
163 HAL_Report(HALUsageReporting::kResourceType_SPI,
164 static_cast<uint8_t>(port) + 1);
165}
166
Austin Schuh812d0d12021-11-04 20:16:48 -0700167SPI::~SPI() {
168 HAL_CloseSPI(m_port);
169}
Brian Silverman8fce7482020-01-05 13:18:21 -0800170
Austin Schuh812d0d12021-11-04 20:16:48 -0700171SPI::Port SPI::GetPort() const {
172 return static_cast<Port>(static_cast<int>(m_port));
173}
174
175void SPI::SetClockRate(int hz) {
176 HAL_SetSPISpeed(m_port, hz);
177}
Brian Silverman8fce7482020-01-05 13:18:21 -0800178
179void SPI::SetMSBFirst() {
180 m_msbFirst = true;
181 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
182}
183
184void SPI::SetLSBFirst() {
185 m_msbFirst = false;
186 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
187}
188
189void SPI::SetSampleDataOnLeadingEdge() {
190 m_sampleOnTrailing = false;
191 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
192}
193
194void SPI::SetSampleDataOnTrailingEdge() {
195 m_sampleOnTrailing = true;
196 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
197}
198
199void SPI::SetSampleDataOnFalling() {
200 m_sampleOnTrailing = true;
201 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
202}
203
204void SPI::SetSampleDataOnRising() {
205 m_sampleOnTrailing = false;
206 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
207}
208
209void SPI::SetClockActiveLow() {
210 m_clockIdleHigh = true;
211 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
212}
213
214void SPI::SetClockActiveHigh() {
215 m_clockIdleHigh = false;
216 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
217}
218
219void SPI::SetChipSelectActiveHigh() {
220 int32_t status = 0;
221 HAL_SetSPIChipSelectActiveHigh(m_port, &status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700222 FRC_CheckErrorStatus(status, "Port {}", m_port);
Brian Silverman8fce7482020-01-05 13:18:21 -0800223}
224
225void SPI::SetChipSelectActiveLow() {
226 int32_t status = 0;
227 HAL_SetSPIChipSelectActiveLow(m_port, &status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700228 FRC_CheckErrorStatus(status, "Port {}", m_port);
Brian Silverman8fce7482020-01-05 13:18:21 -0800229}
230
231int SPI::Write(uint8_t* data, int size) {
232 int retVal = 0;
233 retVal = HAL_WriteSPI(m_port, data, size);
234 return retVal;
235}
236
237int SPI::Read(bool initiate, uint8_t* dataReceived, int size) {
238 int retVal = 0;
239 if (initiate) {
240 wpi::SmallVector<uint8_t, 32> dataToSend;
241 dataToSend.resize(size);
242 retVal = HAL_TransactionSPI(m_port, dataToSend.data(), dataReceived, size);
243 } else {
244 retVal = HAL_ReadSPI(m_port, dataReceived, size);
245 }
246 return retVal;
247}
248
249int SPI::Transaction(uint8_t* dataToSend, uint8_t* dataReceived, int size) {
250 int retVal = 0;
251 retVal = HAL_TransactionSPI(m_port, dataToSend, dataReceived, size);
252 return retVal;
253}
254
255void SPI::InitAuto(int bufferSize) {
256 int32_t status = 0;
257 HAL_InitSPIAuto(m_port, bufferSize, &status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700258 FRC_CheckErrorStatus(status, "Port {}", m_port);
Brian Silverman8fce7482020-01-05 13:18:21 -0800259}
260
261void SPI::FreeAuto() {
262 int32_t status = 0;
263 HAL_FreeSPIAuto(m_port, &status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700264 FRC_CheckErrorStatus(status, "Port {}", m_port);
Brian Silverman8fce7482020-01-05 13:18:21 -0800265}
266
Austin Schuh812d0d12021-11-04 20:16:48 -0700267void SPI::SetAutoTransmitData(wpi::span<const uint8_t> dataToSend,
268 int zeroSize) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800269 int32_t status = 0;
270 HAL_SetSPIAutoTransmitData(m_port, dataToSend.data(), dataToSend.size(),
271 zeroSize, &status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700272 FRC_CheckErrorStatus(status, "Port {}", m_port);
Brian Silverman8fce7482020-01-05 13:18:21 -0800273}
274
275void SPI::StartAutoRate(units::second_t period) {
276 int32_t status = 0;
Austin Schuh812d0d12021-11-04 20:16:48 -0700277 HAL_StartSPIAutoRate(m_port, period.value(), &status);
278 FRC_CheckErrorStatus(status, "Port {}", m_port);
Brian Silverman8fce7482020-01-05 13:18:21 -0800279}
280
281void SPI::StartAutoRate(double period) {
282 StartAutoRate(units::second_t(period));
283}
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);
291 FRC_CheckErrorStatus(status, "Port {}", 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);
Austin Schuh812d0d12021-11-04 20:16:48 -0700297 FRC_CheckErrorStatus(status, "Port {}", 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);
Austin Schuh812d0d12021-11-04 20:16:48 -0700303 FRC_CheckErrorStatus(status, "Port {}", 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);
311 FRC_CheckErrorStatus(status, "Port {}", 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);
Austin Schuh812d0d12021-11-04 20:16:48 -0700318 FRC_CheckErrorStatus(status, "Port {}", 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);
Austin Schuh812d0d12021-11-04 20:16:48 -0700327 FRC_CheckErrorStatus(status, "Port {}", 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
358void SPI::InitAccumulator(double period, int cmd, int xferSize, int validMask,
359 int validValue, int dataShift, int dataSize,
360 bool isSigned, bool bigEndian) {
361 InitAccumulator(units::second_t(period), cmd, xferSize, validMask, validValue,
362 dataShift, dataSize, isSigned, bigEndian);
363}
364
365void SPI::FreeAccumulator() {
366 m_accum.reset(nullptr);
367 FreeAuto();
368}
369
370void SPI::ResetAccumulator() {
Austin Schuh812d0d12021-11-04 20:16:48 -0700371 if (!m_accum) {
372 return;
373 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800374 std::scoped_lock lock(m_accum->m_mutex);
375 m_accum->m_value = 0;
376 m_accum->m_count = 0;
377 m_accum->m_lastValue = 0;
378 m_accum->m_lastTimestamp = 0;
379 m_accum->m_integratedValue = 0;
380}
381
382void SPI::SetAccumulatorCenter(int center) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700383 if (!m_accum) {
384 return;
385 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800386 std::scoped_lock lock(m_accum->m_mutex);
387 m_accum->m_center = center;
388}
389
390void SPI::SetAccumulatorDeadband(int deadband) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700391 if (!m_accum) {
392 return;
393 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800394 std::scoped_lock lock(m_accum->m_mutex);
395 m_accum->m_deadband = deadband;
396}
397
398int SPI::GetAccumulatorLastValue() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700399 if (!m_accum) {
400 return 0;
401 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800402 std::scoped_lock lock(m_accum->m_mutex);
403 m_accum->Update();
404 return m_accum->m_lastValue;
405}
406
407int64_t SPI::GetAccumulatorValue() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700408 if (!m_accum) {
409 return 0;
410 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800411 std::scoped_lock lock(m_accum->m_mutex);
412 m_accum->Update();
413 return m_accum->m_value;
414}
415
416int64_t SPI::GetAccumulatorCount() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700417 if (!m_accum) {
418 return 0;
419 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800420 std::scoped_lock lock(m_accum->m_mutex);
421 m_accum->Update();
422 return m_accum->m_count;
423}
424
425double SPI::GetAccumulatorAverage() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700426 if (!m_accum) {
427 return 0;
428 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800429 std::scoped_lock lock(m_accum->m_mutex);
430 m_accum->Update();
Austin Schuh812d0d12021-11-04 20:16:48 -0700431 if (m_accum->m_count == 0) {
432 return 0.0;
433 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800434 return static_cast<double>(m_accum->m_value) / m_accum->m_count;
435}
436
437void SPI::GetAccumulatorOutput(int64_t& value, int64_t& count) const {
438 if (!m_accum) {
439 value = 0;
440 count = 0;
441 return;
442 }
443 std::scoped_lock lock(m_accum->m_mutex);
444 m_accum->Update();
445 value = m_accum->m_value;
446 count = m_accum->m_count;
447}
448
449void SPI::SetAccumulatorIntegratedCenter(double center) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700450 if (!m_accum) {
451 return;
452 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800453 std::scoped_lock lock(m_accum->m_mutex);
454 m_accum->m_integratedCenter = center;
455}
456
457double SPI::GetAccumulatorIntegratedValue() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700458 if (!m_accum) {
459 return 0;
460 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800461 std::scoped_lock lock(m_accum->m_mutex);
462 m_accum->Update();
463 return m_accum->m_integratedValue;
464}
465
466double SPI::GetAccumulatorIntegratedAverage() const {
Austin Schuh812d0d12021-11-04 20:16:48 -0700467 if (!m_accum) {
468 return 0;
469 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800470 std::scoped_lock lock(m_accum->m_mutex);
471 m_accum->Update();
Austin Schuh812d0d12021-11-04 20:16:48 -0700472 if (m_accum->m_count <= 1) {
473 return 0.0;
474 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800475 // count-1 due to not integrating the first value received
476 return m_accum->m_integratedValue / (m_accum->m_count - 1);
477}