blob: 54cf82b1964e296b94520dd96bb954bf60804179 [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
13#include <hal/HAL.h>
14#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);
158 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
159
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800160 HAL_Report(HALUsageReporting::kResourceType_SPI, port);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800161}
162
163SPI::~SPI() { HAL_CloseSPI(m_port); }
164
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800165void SPI::SetClockRate(int hz) { HAL_SetSPISpeed(m_port, hz); }
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800166
167void SPI::SetMSBFirst() {
168 m_msbFirst = true;
169 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
170}
171
172void SPI::SetLSBFirst() {
173 m_msbFirst = false;
174 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
175}
176
177void SPI::SetSampleDataOnLeadingEdge() {
178 m_sampleOnTrailing = false;
179 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
180}
181
182void SPI::SetSampleDataOnTrailingEdge() {
183 m_sampleOnTrailing = true;
184 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
185}
186
187void SPI::SetSampleDataOnFalling() {
188 m_sampleOnTrailing = true;
189 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
190}
191
192void SPI::SetSampleDataOnRising() {
193 m_sampleOnTrailing = false;
194 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
195}
196
197void SPI::SetClockActiveLow() {
198 m_clockIdleHigh = true;
199 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
200}
201
202void SPI::SetClockActiveHigh() {
203 m_clockIdleHigh = false;
204 HAL_SetSPIOpts(m_port, m_msbFirst, m_sampleOnTrailing, m_clockIdleHigh);
205}
206
207void SPI::SetChipSelectActiveHigh() {
208 int32_t status = 0;
209 HAL_SetSPIChipSelectActiveHigh(m_port, &status);
210 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
211}
212
213void SPI::SetChipSelectActiveLow() {
214 int32_t status = 0;
215 HAL_SetSPIChipSelectActiveLow(m_port, &status);
216 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
217}
218
219int SPI::Write(uint8_t* data, int size) {
220 int retVal = 0;
221 retVal = HAL_WriteSPI(m_port, data, size);
222 return retVal;
223}
224
225int SPI::Read(bool initiate, uint8_t* dataReceived, int size) {
226 int retVal = 0;
227 if (initiate) {
228 wpi::SmallVector<uint8_t, 32> dataToSend;
229 dataToSend.resize(size);
230 retVal = HAL_TransactionSPI(m_port, dataToSend.data(), dataReceived, size);
231 } else {
232 retVal = HAL_ReadSPI(m_port, dataReceived, size);
233 }
234 return retVal;
235}
236
237int SPI::Transaction(uint8_t* dataToSend, uint8_t* dataReceived, int size) {
238 int retVal = 0;
239 retVal = HAL_TransactionSPI(m_port, dataToSend, dataReceived, size);
240 return retVal;
241}
242
243void SPI::InitAuto(int bufferSize) {
244 int32_t status = 0;
245 HAL_InitSPIAuto(m_port, bufferSize, &status);
246 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
247}
248
249void SPI::FreeAuto() {
250 int32_t status = 0;
251 HAL_FreeSPIAuto(m_port, &status);
252 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
253}
254
255void SPI::SetAutoTransmitData(wpi::ArrayRef<uint8_t> dataToSend, int zeroSize) {
256 int32_t status = 0;
257 HAL_SetSPIAutoTransmitData(m_port, dataToSend.data(), dataToSend.size(),
258 zeroSize, &status);
259 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
260}
261
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800262void SPI::StartAutoRate(units::second_t period) {
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800263 int32_t status = 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800264 HAL_StartSPIAutoRate(m_port, period.to<double>(), &status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800265 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
266}
267
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800268void SPI::StartAutoRate(double period) {
269 StartAutoRate(units::second_t(period));
270}
271
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800272void SPI::StartAutoTrigger(DigitalSource& source, bool rising, bool falling) {
273 int32_t status = 0;
274 HAL_StartSPIAutoTrigger(
275 m_port, source.GetPortHandleForRouting(),
276 (HAL_AnalogTriggerType)source.GetAnalogTriggerTypeForRouting(), rising,
277 falling, &status);
278 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
279}
280
281void SPI::StopAuto() {
282 int32_t status = 0;
283 HAL_StopSPIAuto(m_port, &status);
284 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
285}
286
287void SPI::ForceAutoRead() {
288 int32_t status = 0;
289 HAL_ForceSPIAutoRead(m_port, &status);
290 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
291}
292
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800293int SPI::ReadAutoReceivedData(uint32_t* buffer, int numToRead,
294 units::second_t timeout) {
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800295 int32_t status = 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800296 int32_t val = HAL_ReadSPIAutoReceivedData(m_port, buffer, numToRead,
297 timeout.to<double>(), &status);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800298 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
299 return val;
300}
301
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800302int SPI::ReadAutoReceivedData(uint32_t* buffer, int numToRead, double timeout) {
303 return ReadAutoReceivedData(buffer, numToRead, units::second_t(timeout));
304}
305
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800306int SPI::GetAutoDroppedCount() {
307 int32_t status = 0;
308 int32_t val = HAL_GetSPIAutoDroppedCount(m_port, &status);
309 wpi_setErrorWithContext(status, HAL_GetErrorMessage(status));
310 return val;
311}
312
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800313void SPI::InitAccumulator(units::second_t period, int cmd, int xferSize,
314 int validMask, int validValue, int dataShift,
315 int dataSize, bool isSigned, bool bigEndian) {
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800316 InitAuto(xferSize * kAccumulateDepth);
317 uint8_t cmdBytes[4] = {0, 0, 0, 0};
318 if (bigEndian) {
319 for (int32_t i = xferSize - 1; i >= 0; --i) {
320 cmdBytes[i] = cmd & 0xff;
321 cmd >>= 8;
322 }
323 } else {
324 cmdBytes[0] = cmd & 0xff;
325 cmd >>= 8;
326 cmdBytes[1] = cmd & 0xff;
327 cmd >>= 8;
328 cmdBytes[2] = cmd & 0xff;
329 cmd >>= 8;
330 cmdBytes[3] = cmd & 0xff;
331 }
332 SetAutoTransmitData(cmdBytes, xferSize - 4);
333 StartAutoRate(period);
334
335 m_accum.reset(new Accumulator(m_port, xferSize, validMask, validValue,
336 dataShift, dataSize, isSigned, bigEndian));
337 m_accum->m_notifier.StartPeriodic(period * kAccumulateDepth / 2);
338}
339
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800340void SPI::InitAccumulator(double period, int cmd, int xferSize, int validMask,
341 int validValue, int dataShift, int dataSize,
342 bool isSigned, bool bigEndian) {
343 InitAccumulator(units::second_t(period), cmd, xferSize, validMask, validValue,
344 dataShift, dataSize, isSigned, bigEndian);
345}
346
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800347void SPI::FreeAccumulator() {
348 m_accum.reset(nullptr);
349 FreeAuto();
350}
351
352void SPI::ResetAccumulator() {
353 if (!m_accum) return;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800354 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800355 m_accum->m_value = 0;
356 m_accum->m_count = 0;
357 m_accum->m_lastValue = 0;
358 m_accum->m_lastTimestamp = 0;
359 m_accum->m_integratedValue = 0;
360}
361
362void SPI::SetAccumulatorCenter(int center) {
363 if (!m_accum) return;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800364 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800365 m_accum->m_center = center;
366}
367
368void SPI::SetAccumulatorDeadband(int deadband) {
369 if (!m_accum) return;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800370 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800371 m_accum->m_deadband = deadband;
372}
373
374int SPI::GetAccumulatorLastValue() const {
375 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800376 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800377 m_accum->Update();
378 return m_accum->m_lastValue;
379}
380
381int64_t SPI::GetAccumulatorValue() const {
382 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800383 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800384 m_accum->Update();
385 return m_accum->m_value;
386}
387
388int64_t SPI::GetAccumulatorCount() const {
389 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800390 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800391 m_accum->Update();
392 return m_accum->m_count;
393}
394
395double SPI::GetAccumulatorAverage() const {
396 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800397 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800398 m_accum->Update();
399 if (m_accum->m_count == 0) return 0.0;
400 return static_cast<double>(m_accum->m_value) / m_accum->m_count;
401}
402
403void SPI::GetAccumulatorOutput(int64_t& value, int64_t& count) const {
404 if (!m_accum) {
405 value = 0;
406 count = 0;
407 return;
408 }
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800409 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800410 m_accum->Update();
411 value = m_accum->m_value;
412 count = m_accum->m_count;
413}
414
415void SPI::SetAccumulatorIntegratedCenter(double center) {
416 if (!m_accum) return;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800417 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800418 m_accum->m_integratedCenter = center;
419}
420
421double SPI::GetAccumulatorIntegratedValue() const {
422 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800423 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800424 m_accum->Update();
425 return m_accum->m_integratedValue;
426}
427
428double SPI::GetAccumulatorIntegratedAverage() const {
429 if (!m_accum) return 0;
James Kuszmaul4f3ad3c2019-12-01 16:35:21 -0800430 std::scoped_lock lock(m_accum->m_mutex);
Brian Silverman41cdd3e2019-01-19 19:48:58 -0800431 m_accum->Update();
432 if (m_accum->m_count <= 1) return 0.0;
433 // count-1 due to not integrating the first value received
434 return m_accum->m_integratedValue / (m_accum->m_count - 1);
435}