blob: 074cc7943226e76ba4ec98780cb1cdebd34b14dd [file] [log] [blame]
Brian Silverman41cdd3e2019-01-19 19:48:58 -08001/*----------------------------------------------------------------------------*/
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -08002/* Copyright (c) 2008-2019 FIRST. All Rights Reserved. */
Brian Silverman41cdd3e2019-01-19 19:48:58 -08003/* 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 "frc/SPI.h"
9
10#include <cstring>
11#include <utility>
12
James Kuszmaul4b81d302019-12-14 20:53:14 -080013#include <hal/FRCUsageReporting.h>
Brian Silverman41cdd3e2019-01-19 19:48:58 -080014#include <hal/SPI.h>
15#include <wpi/SmallVector.h>
16#include <wpi/mutex.h>
17
18#include "frc/DigitalSource.h"
19#include "frc/Notifier.h"
20#include "frc/WPIErrors.h"
21
22using namespace frc;
23
24static constexpr int kAccumulateDepth = 2048;
25
26class SPI::Accumulator {
27 public:
28 Accumulator(HAL_SPIPort port, int xferSize, int validMask, int validValue,
29 int dataShift, int dataSize, bool isSigned, bool bigEndian)
30 : m_notifier([=]() {
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -080031 std::scoped_lock lock(m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -080032 Update();
33 }),
34 m_buf(new uint32_t[(xferSize + 1) * kAccumulateDepth]),
35 m_validMask(validMask),
36 m_validValue(validValue),
37 m_dataMax(1 << dataSize),
38 m_dataMsbMask(1 << (dataSize - 1)),
39 m_dataShift(dataShift),
40 m_xferSize(xferSize + 1), // +1 for timestamp
41 m_isSigned(isSigned),
42 m_bigEndian(bigEndian),
43 m_port(port) {}
44 ~Accumulator() { delete[] m_buf; }
45
46 void Update();
47
48 Notifier m_notifier;
49 uint32_t* m_buf;
50 wpi::mutex m_mutex;
51
52 int64_t m_value = 0;
53 uint32_t m_count = 0;
54 int32_t m_lastValue = 0;
55 uint32_t m_lastTimestamp = 0;
56 double m_integratedValue = 0;
57
58 int32_t m_center = 0;
59 int32_t m_deadband = 0;
60 double m_integratedCenter = 0;
61
62 int32_t m_validMask;
63 int32_t m_validValue;
64 int32_t m_dataMax; // one more than max data value
65 int32_t m_dataMsbMask; // data field MSB mask (for signed)
66 uint8_t m_dataShift; // data field shift right amount, in bits
67 int32_t m_xferSize; // SPI transfer size, in bytes
68 bool m_isSigned; // is data field signed?
69 bool m_bigEndian; // is response big endian?
70 HAL_SPIPort m_port;
71};
72
73void SPI::Accumulator::Update() {
74 bool done;
75 do {
76 done = true;
77 int32_t status = 0;
78
79 // get amount of data available
80 int32_t numToRead =
81 HAL_ReadSPIAutoReceivedData(m_port, m_buf, 0, 0, &status);
82 if (status != 0) return; // error reading
83
84 // only get whole responses; +1 is for timestamp
85 numToRead -= numToRead % m_xferSize;
86 if (numToRead > m_xferSize * kAccumulateDepth) {
87 numToRead = m_xferSize * kAccumulateDepth;
88 done = false;
89 }
90 if (numToRead == 0) return; // no samples
91
92 // read buffered data
93 HAL_ReadSPIAutoReceivedData(m_port, m_buf, numToRead, 0, &status);
94 if (status != 0) return; // error reading
95
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
121 if (m_isSigned && (data & m_dataMsbMask) != 0) data -= m_dataMax;
122 // center offset
123 int32_t dataNoCenter = data;
124 data -= m_center;
125 // only accumulate if outside deadband
126 if (data < -m_deadband || data > m_deadband) {
127 m_value += data;
128 if (m_count != 0) {
129 // timestamps use the 1us FPGA clock; also handle rollover
130 if (timestamp >= m_lastTimestamp)
131 m_integratedValue +=
132 dataNoCenter *
133 static_cast<int32_t>(timestamp - m_lastTimestamp) * 1e-6 -
134 m_integratedCenter;
135 else
136 m_integratedValue +=
137 dataNoCenter *
138 static_cast<int32_t>((1ULL << 32) - m_lastTimestamp +
139 timestamp) *
140 1e-6 -
141 m_integratedCenter;
142 }
143 }
144 ++m_count;
145 m_lastValue = data;
146 } else {
147 // no data from the sensor; just clear the last value
148 m_lastValue = 0;
149 }
150 m_lastTimestamp = timestamp;
151 }
152 } while (!done);
153}
154
155SPI::SPI(Port port) : m_port(static_cast<HAL_SPIPort>(port)) {
156 int32_t status = 0;
157 HAL_InitializeSPI(m_port, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800158 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800159
James Kuszmaul4b81d302019-12-14 20:53:14 -0800160 HAL_Report(HALUsageReporting::kResourceType_SPI,
161 static_cast<uint8_t>(port) + 1);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800162}
163
164SPI::~SPI() { HAL_CloseSPI(m_port); }
165
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800166void SPI::SetClockRate(int hz) { HAL_SetSPISpeed(m_port, hz); }
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800167
168void SPI::SetMSBFirst() {
169 m_msbFirst = true;
170 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
171}
172
173void SPI::SetLSBFirst() {
174 m_msbFirst = false;
175 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
176}
177
178void SPI::SetSampleDataOnLeadingEdge() {
179 m_sampleOnTrailing = false;
180 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
181}
182
183void SPI::SetSampleDataOnTrailingEdge() {
184 m_sampleOnTrailing = true;
185 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
186}
187
188void SPI::SetSampleDataOnFalling() {
189 m_sampleOnTrailing = true;
190 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
191}
192
193void SPI::SetSampleDataOnRising() {
194 m_sampleOnTrailing = false;
195 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
196}
197
198void SPI::SetClockActiveLow() {
199 m_clockIdleHigh = true;
200 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
201}
202
203void SPI::SetClockActiveHigh() {
204 m_clockIdleHigh = false;
205 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
206}
207
208void SPI::SetChipSelectActiveHigh() {
209 int32_t status = 0;
210 HAL_SetSPIChipSelectActiveHigh(m_port, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800211 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800212}
213
214void SPI::SetChipSelectActiveLow() {
215 int32_t status = 0;
216 HAL_SetSPIChipSelectActiveLow(m_port, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800217 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800218}
219
220int SPI::Write(uint8_t* data, int size) {
221 int retVal = 0;
222 retVal = HAL_WriteSPI(m_port, data, size);
223 return retVal;
224}
225
226int SPI::Read(bool initiate, uint8_t* dataReceived, int size) {
227 int retVal = 0;
228 if (initiate) {
229 wpi::SmallVector<uint8_t, 32> dataToSend;
230 dataToSend.resize(size);
231 retVal = HAL_TransactionSPI(m_port, dataToSend.data(), dataReceived, size);
232 } else {
233 retVal = HAL_ReadSPI(m_port, dataReceived, size);
234 }
235 return retVal;
236}
237
238int SPI::Transaction(uint8_t* dataToSend, uint8_t* dataReceived, int size) {
239 int retVal = 0;
240 retVal = HAL_TransactionSPI(m_port, dataToSend, dataReceived, size);
241 return retVal;
242}
243
244void SPI::InitAuto(int bufferSize) {
245 int32_t status = 0;
246 HAL_InitSPIAuto(m_port, bufferSize, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800247 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800248}
249
250void SPI::FreeAuto() {
251 int32_t status = 0;
252 HAL_FreeSPIAuto(m_port, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800253 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800254}
255
256void SPI::SetAutoTransmitData(wpi::ArrayRef<uint8_t> dataToSend, int zeroSize) {
257 int32_t status = 0;
258 HAL_SetSPIAutoTransmitData(m_port, dataToSend.data(), dataToSend.size(),
259 zeroSize, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800260 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800261}
262
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800263void SPI::StartAutoRate(units::second_t period) {
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800264 int32_t status = 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800265 HAL_StartSPIAutoRate(m_port, period.to<double>(), &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800266 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800267}
268
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800269void SPI::StartAutoRate(double period) {
270 StartAutoRate(units::second_t(period));
271}
272
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800273void SPI::StartAutoTrigger(DigitalSource& source, bool rising, bool falling) {
274 int32_t status = 0;
275 HAL_StartSPIAutoTrigger(
276 m_port, source.GetPortHandleForRouting(),
277 (HAL_AnalogTriggerType)source.GetAnalogTriggerTypeForRouting(), rising,
278 falling, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800279 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800280}
281
282void SPI::StopAuto() {
283 int32_t status = 0;
284 HAL_StopSPIAuto(m_port, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800285 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800286}
287
288void SPI::ForceAutoRead() {
289 int32_t status = 0;
290 HAL_ForceSPIAutoRead(m_port, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800291 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800292}
293
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800294int SPI::ReadAutoReceivedData(uint32_t* buffer, int numToRead,
295 units::second_t timeout) {
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800296 int32_t status = 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800297 int32_t val = HAL_ReadSPIAutoReceivedData(m_port, buffer, numToRead,
298 timeout.to<double>(), &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800299 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800300 return val;
301}
302
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800303int SPI::ReadAutoReceivedData(uint32_t* buffer, int numToRead, double timeout) {
304 return ReadAutoReceivedData(buffer, numToRead, units::second_t(timeout));
305}
306
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800307int SPI::GetAutoDroppedCount() {
308 int32_t status = 0;
309 int32_t val = HAL_GetSPIAutoDroppedCount(m_port, &status);
James Kuszmaul4b81d302019-12-14 20:53:14 -0800310 wpi_setHALError(status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800311 return val;
312}
313
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800314void SPI::InitAccumulator(units::second_t period, int cmd, int xferSize,
315 int validMask, int validValue, int dataShift,
316 int dataSize, bool isSigned, bool bigEndian) {
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800317 InitAuto(xferSize * kAccumulateDepth);
318 uint8_t cmdBytes[4] = {0, 0, 0, 0};
319 if (bigEndian) {
320 for (int32_t i = xferSize - 1; i >= 0; --i) {
321 cmdBytes[i] = cmd & 0xff;
322 cmd >>= 8;
323 }
324 } else {
325 cmdBytes[0] = cmd & 0xff;
326 cmd >>= 8;
327 cmdBytes[1] = cmd & 0xff;
328 cmd >>= 8;
329 cmdBytes[2] = cmd & 0xff;
330 cmd >>= 8;
331 cmdBytes[3] = cmd & 0xff;
332 }
333 SetAutoTransmitData(cmdBytes, xferSize - 4);
334 StartAutoRate(period);
335
336 m_accum.reset(new Accumulator(m_port, xferSize, validMask, validValue,
337 dataShift, dataSize, isSigned, bigEndian));
338 m_accum->m_notifier.StartPeriodic(period * kAccumulateDepth / 2);
339}
340
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800341void SPI::InitAccumulator(double period, int cmd, int xferSize, int validMask,
342 int validValue, int dataShift, int dataSize,
343 bool isSigned, bool bigEndian) {
344 InitAccumulator(units::second_t(period), cmd, xferSize, validMask, validValue,
345 dataShift, dataSize, isSigned, bigEndian);
346}
347
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800348void SPI::FreeAccumulator() {
349 m_accum.reset(nullptr);
350 FreeAuto();
351}
352
353void SPI::ResetAccumulator() {
354 if (!m_accum) return;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800355 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800356 m_accum->m_value = 0;
357 m_accum->m_count = 0;
358 m_accum->m_lastValue = 0;
359 m_accum->m_lastTimestamp = 0;
360 m_accum->m_integratedValue = 0;
361}
362
363void SPI::SetAccumulatorCenter(int center) {
364 if (!m_accum) return;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800365 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800366 m_accum->m_center = center;
367}
368
369void SPI::SetAccumulatorDeadband(int deadband) {
370 if (!m_accum) return;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800371 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800372 m_accum->m_deadband = deadband;
373}
374
375int SPI::GetAccumulatorLastValue() const {
376 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800377 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800378 m_accum->Update();
379 return m_accum->m_lastValue;
380}
381
382int64_t SPI::GetAccumulatorValue() const {
383 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800384 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800385 m_accum->Update();
386 return m_accum->m_value;
387}
388
389int64_t SPI::GetAccumulatorCount() const {
390 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800391 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800392 m_accum->Update();
393 return m_accum->m_count;
394}
395
396double SPI::GetAccumulatorAverage() const {
397 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800398 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800399 m_accum->Update();
400 if (m_accum->m_count == 0) return 0.0;
401 return static_cast<double>(m_accum->m_value) / m_accum->m_count;
402}
403
404void SPI::GetAccumulatorOutput(int64_t& value, int64_t& count) const {
405 if (!m_accum) {
406 value = 0;
407 count = 0;
408 return;
409 }
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800410 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800411 m_accum->Update();
412 value = m_accum->m_value;
413 count = m_accum->m_count;
414}
415
416void SPI::SetAccumulatorIntegratedCenter(double center) {
417 if (!m_accum) return;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800418 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800419 m_accum->m_integratedCenter = center;
420}
421
422double SPI::GetAccumulatorIntegratedValue() const {
423 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800424 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800425 m_accum->Update();
426 return m_accum->m_integratedValue;
427}
428
429double SPI::GetAccumulatorIntegratedAverage() const {
430 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800431 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800432 m_accum->Update();
433 if (m_accum->m_count <= 1) return 0.0;
434 // count-1 due to not integrating the first value received
435 return m_accum->m_integratedValue / (m_accum->m_count - 1);
436}