blob: 148ff291ea67ed26b33723edb08a0360adbbc23f [file] [log] [blame]
Brian Silverman2aa83d72015-01-24 18:03:11 -05001#include "dma.h"
2
Austin Schuh58d8cdf2015-02-15 21:04:42 -08003#include <string.h>
4
Brian Silverman2aa83d72015-01-24 18:03:11 -05005#include <algorithm>
Brian Silvermand49fd782015-01-30 16:43:17 -05006#include <type_traits>
7
8#include "DigitalSource.h"
9#include "AnalogInput.h"
10#include "Encoder.h"
11
Austin Schuh58d8cdf2015-02-15 21:04:42 -080012
Brian Silvermand49fd782015-01-30 16:43:17 -050013// Interface to the roboRIO FPGA's DMA features.
Brian Silverman2aa83d72015-01-24 18:03:11 -050014
15// Like tEncoder::tOutput with the bitfields reversed.
16typedef union {
17 struct {
18 unsigned Direction: 1;
19 signed Value: 31;
20 };
21 struct {
22 unsigned value: 32;
23 };
24} t1Output;
25
26static const uint32_t kNumHeaders = 10;
27
28static constexpr ssize_t kChannelSize[18] = {2, 2, 4, 4, 2, 2, 4, 4, 3,
29 3, 2, 1, 4, 4, 4, 4, 4, 4};
30
31enum DMAOffsetConstants {
32 kEnable_AI0_Low = 0,
33 kEnable_AI0_High = 1,
34 kEnable_AIAveraged0_Low = 2,
35 kEnable_AIAveraged0_High = 3,
36 kEnable_AI1_Low = 4,
37 kEnable_AI1_High = 5,
38 kEnable_AIAveraged1_Low = 6,
39 kEnable_AIAveraged1_High = 7,
40 kEnable_Accumulator0 = 8,
41 kEnable_Accumulator1 = 9,
42 kEnable_DI = 10,
43 kEnable_AnalogTriggers = 11,
44 kEnable_Counters_Low = 12,
45 kEnable_Counters_High = 13,
46 kEnable_CounterTimers_Low = 14,
47 kEnable_CounterTimers_High = 15,
48 kEnable_Encoders = 16,
49 kEnable_EncoderTimers = 17,
50};
51
52DMA::DMA() {
53 tRioStatusCode status = 0;
54 tdma_config_ = tDMA::create(&status);
Austin Schuh58d8cdf2015-02-15 21:04:42 -080055 tdma_config_->writeConfig_ExternalClock(false, &status);
Brian Silverman2aa83d72015-01-24 18:03:11 -050056 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Austin Schuh58d8cdf2015-02-15 21:04:42 -080057 NiFpga_WriteU32(0x10000, 0x1832c, 0x0);
Brian Silverman2aa83d72015-01-24 18:03:11 -050058 if (status != 0) {
59 return;
60 }
61 SetRate(1);
62 SetPause(false);
63}
64
65DMA::~DMA() {
66 tRioStatusCode status = 0;
67
68 manager_->stop(&status);
69 delete tdma_config_;
70}
71
72void DMA::SetPause(bool pause) {
73 tRioStatusCode status = 0;
74 tdma_config_->writeConfig_Pause(pause, &status);
75 wpi_setErrorWithContext(status, getHALErrorMessage(status));
76}
77
78void DMA::SetRate(uint32_t cycles) {
79 if (cycles < 1) {
80 cycles = 1;
81 }
82 tRioStatusCode status = 0;
83 tdma_config_->writeRate(cycles, &status);
84 wpi_setErrorWithContext(status, getHALErrorMessage(status));
85}
86
Austin Schuh58d8cdf2015-02-15 21:04:42 -080087void DMA::Add(Encoder *encoder) {
Brian Silverman2aa83d72015-01-24 18:03:11 -050088 tRioStatusCode status = 0;
89
90 if (manager_) {
91 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
92 "DMA::Add() only works before DMA::Start()");
93 return;
94 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -080095 if (encoder->GetFPGAIndex() >= 4) {
96 wpi_setErrorWithContext(
97 NiFpga_Status_InvalidParameter,
98 "FPGA encoder index is not in the 4 that get logged.");
99 }
Brian Silverman2aa83d72015-01-24 18:03:11 -0500100
101 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
102 tdma_config_->writeConfig_Enable_Encoders(true, &status);
103 wpi_setErrorWithContext(status, getHALErrorMessage(status));
104}
105
106void DMA::Add(DigitalSource * /*input*/) {
107 tRioStatusCode status = 0;
108
109 if (manager_) {
110 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
111 "DMA::Add() only works before DMA::Start()");
112 return;
113 }
114
115 tdma_config_->writeConfig_Enable_DI(true, &status);
116 wpi_setErrorWithContext(status, getHALErrorMessage(status));
117}
118
Brian Silvermand49fd782015-01-30 16:43:17 -0500119void DMA::Add(AnalogInput *input) {
120 tRioStatusCode status = 0;
121
122 if (manager_) {
123 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
124 "DMA::Add() only works before DMA::Start()");
125 return;
126 }
127
Brian Silvermand49fd782015-01-30 16:43:17 -0500128 if (input->GetChannel() <= 3) {
129 tdma_config_->writeConfig_Enable_AI0_Low(true, &status);
130 } else {
131 tdma_config_->writeConfig_Enable_AI0_High(true, &status);
132 }
133 wpi_setErrorWithContext(status, getHALErrorMessage(status));
134}
135
Brian Silverman2aa83d72015-01-24 18:03:11 -0500136void DMA::SetExternalTrigger(DigitalSource *input, bool rising, bool falling) {
137 tRioStatusCode status = 0;
138
139 if (manager_) {
140 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
141 "DMA::SetExternalTrigger() only works before DMA::Start()");
142 return;
143 }
144
145 auto index =
146 ::std::find(trigger_channels_.begin(), trigger_channels_.end(), false);
147 if (index == trigger_channels_.end()) {
148 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800149 "DMA: No channels left");
Brian Silverman2aa83d72015-01-24 18:03:11 -0500150 return;
151 }
152 *index = true;
153
154 const int channel_index = index - trigger_channels_.begin();
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800155 /*
156 printf(
157 "Allocating trigger on index %d, routing module %d, routing channel %d, "
158 "is analog %d\n",
159 channel_index, input->GetModuleForRouting(),
160 input->GetChannelForRouting(), input->GetAnalogTriggerForRouting());
161 */
Brian Silverman2aa83d72015-01-24 18:03:11 -0500162
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800163 const bool is_external_clock =
164 tdma_config_->readConfig_ExternalClock(&status);
165 if (status == 0) {
166 if (!is_external_clock) {
167 tdma_config_->writeConfig_ExternalClock(true, &status);
168 wpi_setErrorWithContext(status, getHALErrorMessage(status));
169 if (status != 0) {
170 return;
171 }
172 }
173 } else {
174 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500175 return;
176 }
177
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800178 nFPGA::nFRC_2015_1_0_A::tDMA::tExternalTriggers new_trigger;
179
180 new_trigger.FallingEdge = falling;
181 new_trigger.RisingEdge = rising;
182 new_trigger.ExternalClockSource_AnalogTrigger =
183 input->GetAnalogTriggerForRouting();
184 new_trigger.ExternalClockSource_AnalogTrigger = false;
185 new_trigger.ExternalClockSource_Module = input->GetModuleForRouting();
186 new_trigger.ExternalClockSource_Channel = input->GetChannelForRouting();
187
Brian Silverman2aa83d72015-01-24 18:03:11 -0500188 // Configures the trigger to be external, not off the FPGA clock.
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800189 /*
190 tdma_config_->writeExternalTriggers(channel_index, new_trigger, &status);
Brian Silverman2aa83d72015-01-24 18:03:11 -0500191 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800192 */
Brian Silverman2aa83d72015-01-24 18:03:11 -0500193
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800194 uint32_t current_triggers;
195 tRioStatusCode register_status = NiFpga_ReadU32(0x10000, 0x1832c, &current_triggers);
196 if (register_status != 0) {
197 wpi_setErrorWithContext(register_status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500198 return;
199 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800200 current_triggers = (current_triggers & ~(0xff << (channel_index * 8))) |
201 (new_trigger.value << (channel_index * 8));
202 register_status = NiFpga_WriteU32(0x10000, 0x1832c, current_triggers);
203 if (register_status != 0) {
204 wpi_setErrorWithContext(register_status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500205 return;
206 }
207}
208
209DMA::ReadStatus DMA::Read(DMASample *sample, uint32_t timeout_ms,
Brian Silvermand49fd782015-01-30 16:43:17 -0500210 size_t *remaining_out) {
Brian Silverman2aa83d72015-01-24 18:03:11 -0500211 tRioStatusCode status = 0;
212 size_t remainingBytes = 0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500213 *remaining_out = 0;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500214
215 if (!manager_.get()) {
216 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
217 "DMA::Read() only works after DMA::Start()");
218 return STATUS_ERROR;
219 }
220
Brian Silvermand49fd782015-01-30 16:43:17 -0500221 sample->dma_ = this;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800222 // memset(&sample->read_buffer_, 0, sizeof(sample->read_buffer_));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500223 manager_->read(sample->read_buffer_, capture_size_, timeout_ms,
224 &remainingBytes, &status);
225
226 if (0) { // DEBUG
227 printf("buf[] = ");
228 for (size_t i = 0;
229 i < sizeof(sample->read_buffer_) / sizeof(sample->read_buffer_[0]);
230 ++i) {
231 if (i != 0) {
232 printf(" ");
233 }
234 printf("0x%.8x", sample->read_buffer_[i]);
235 }
236 printf("\n");
237 }
238
239 // TODO(jerry): Do this only if status == 0?
Brian Silvermand49fd782015-01-30 16:43:17 -0500240 *remaining_out = remainingBytes / capture_size_;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500241
242 if (0) { // DEBUG
Brian Silvermand49fd782015-01-30 16:43:17 -0500243 printf("Remaining samples = %d\n", *remaining_out);
Brian Silverman2aa83d72015-01-24 18:03:11 -0500244 }
245
246 // TODO(austin): Check that *remainingBytes % capture_size_ == 0 and deal
247 // with it if it isn't. Probably meant that we overflowed?
248 if (status == 0) {
249 return STATUS_OK;
250 } else if (status == NiFpga_Status_FifoTimeout) {
251 return STATUS_TIMEOUT;
252 } else {
253 wpi_setErrorWithContext(status, getHALErrorMessage(status));
254 return STATUS_ERROR;
255 }
256}
257
Brian Silvermand49fd782015-01-30 16:43:17 -0500258const char *DMA::NameOfReadStatus(ReadStatus s) {
259 switch (s) {
260 case STATUS_OK: return "OK";
261 case STATUS_TIMEOUT: return "TIMEOUT";
262 case STATUS_ERROR: return "ERROR";
263 default: return "(bad ReadStatus code)";
264 }
265}
266
Brian Silverman2aa83d72015-01-24 18:03:11 -0500267void DMA::Start(size_t queue_depth) {
268 tRioStatusCode status = 0;
269 tconfig_ = tdma_config_->readConfig(&status);
270 wpi_setErrorWithContext(status, getHALErrorMessage(status));
271 if (status != 0) {
272 return;
273 }
274
275 {
276 size_t accum_size = 0;
277#define SET_SIZE(bit) \
278 if (tconfig_.bit) { \
279 channel_offsets_[k##bit] = accum_size; \
280 accum_size += kChannelSize[k##bit]; \
281 } else { \
282 channel_offsets_[k##bit] = -1; \
283 }
284
285 SET_SIZE(Enable_AI0_Low);
286 SET_SIZE(Enable_AI0_High);
287 SET_SIZE(Enable_AIAveraged0_Low);
288 SET_SIZE(Enable_AIAveraged0_High);
289 SET_SIZE(Enable_AI1_Low);
290 SET_SIZE(Enable_AI1_High);
291 SET_SIZE(Enable_AIAveraged1_Low);
292 SET_SIZE(Enable_AIAveraged1_High);
293 SET_SIZE(Enable_Accumulator0);
294 SET_SIZE(Enable_Accumulator1);
295 SET_SIZE(Enable_DI);
296 SET_SIZE(Enable_AnalogTriggers);
297 SET_SIZE(Enable_Counters_Low);
298 SET_SIZE(Enable_Counters_High);
299 SET_SIZE(Enable_CounterTimers_Low);
300 SET_SIZE(Enable_CounterTimers_High);
301 SET_SIZE(Enable_Encoders);
302 SET_SIZE(Enable_EncoderTimers);
303#undef SET_SIZE
304 capture_size_ = accum_size + 1;
305 }
306
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800307 manager_.reset(
308 new nFPGA::tDMAManager(0, queue_depth * capture_size_, &status));
309
310 if (0) {
311 for (int i = 0; i < 4; ++i) {
312 tRioStatusCode status = 0;
313 auto x = tdma_config_->readExternalTriggers(i, &status);
314 printf(
315 "index %d, FallingEdge: %d, RisingEdge: %d, "
316 "ExternalClockSource_AnalogTrigger: %d, ExternalClockSource_Module: "
317 "%d, ExternalClockSource_Channel: %d\n",
318 i, x.FallingEdge, x.RisingEdge, x.ExternalClockSource_AnalogTrigger,
319 x.ExternalClockSource_Module, x.ExternalClockSource_Channel);
320 if (status != 0) {
321 wpi_setErrorWithContext(status, getHALErrorMessage(status));
322 }
323 }
324 }
325
Brian Silverman2aa83d72015-01-24 18:03:11 -0500326 wpi_setErrorWithContext(status, getHALErrorMessage(status));
327 if (status != 0) {
328 return;
329 }
330 // Start, stop, start to clear the buffer.
331 manager_->start(&status);
332 wpi_setErrorWithContext(status, getHALErrorMessage(status));
333 if (status != 0) {
334 return;
335 }
336 manager_->stop(&status);
337 wpi_setErrorWithContext(status, getHALErrorMessage(status));
338 if (status != 0) {
339 return;
340 }
341 manager_->start(&status);
342 wpi_setErrorWithContext(status, getHALErrorMessage(status));
343 if (status != 0) {
344 return;
345 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800346
Brian Silverman2aa83d72015-01-24 18:03:11 -0500347}
348
Brian Silvermand49fd782015-01-30 16:43:17 -0500349static_assert(::std::is_pod<DMASample>::value, "DMASample needs to be POD");
350
Brian Silverman2aa83d72015-01-24 18:03:11 -0500351ssize_t DMASample::offset(int index) const { return dma_->channel_offsets_[index]; }
352
353double DMASample::GetTimestamp() const {
354 return static_cast<double>(read_buffer_[dma_->capture_size_ - 1]) * 0.000001;
355}
356
357bool DMASample::Get(DigitalSource *input) const {
358 if (offset(kEnable_DI) == -1) {
359 wpi_setStaticErrorWithContext(dma_,
360 NiFpga_Status_ResourceNotFound,
361 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
362 return false;
363 }
364 if (input->GetChannelForRouting() < kNumHeaders) {
365 return (read_buffer_[offset(kEnable_DI)] >>
366 input->GetChannelForRouting()) &
367 0x1;
368 } else {
369 return (read_buffer_[offset(kEnable_DI)] >>
370 (input->GetChannelForRouting() + 6)) &
371 0x1;
372 }
373}
374
375int32_t DMASample::GetRaw(Encoder *input) const {
376 if (offset(kEnable_Encoders) == -1) {
377 wpi_setStaticErrorWithContext(dma_,
378 NiFpga_Status_ResourceNotFound,
379 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
380 return -1;
381 }
382
Austin Schuhc6cc4102015-02-15 23:19:53 -0800383 if (input->GetFPGAIndex() >= 4) {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800384 wpi_setStaticErrorWithContext(dma_,
385 NiFpga_Status_ResourceNotFound,
386 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
387 }
388
Brian Silverman2aa83d72015-01-24 18:03:11 -0500389 uint32_t dmaWord =
390 read_buffer_[offset(kEnable_Encoders) + input->GetFPGAIndex()];
391 int32_t result = 0;
392
393 if (1) {
394 // Extract the 31-bit signed tEncoder::tOutput Value using a struct with the
395 // reverse packed field order of tOutput. This gets Value from the high
396 // order 31 bits of output on little-endian ARM using gcc. This works
397 // even though C/C++ doesn't guarantee bitfield order.
398 t1Output output;
399
400 output.value = dmaWord;
401 result = output.Value;
402 } else if (1) {
403 // Extract the 31-bit signed tEncoder::tOutput Value using right-shift.
404 // This works even though C/C++ doesn't guarantee whether signed >> does
Brian Silvermand49fd782015-01-30 16:43:17 -0500405 // arithmetic or logical shift. (dmaWord / 2) would fix that but it rounds.
Brian Silverman2aa83d72015-01-24 18:03:11 -0500406 result = static_cast<int32_t>(dmaWord) >> 1;
407 }
408#if 0 // This approach was recommended but it doesn't return the right value.
409 else {
410 // Byte-reverse the DMA word (big-endian value from the FPGA) then extract
411 // the 31-bit tEncoder::tOutput. This does not return the right Value.
412 tEncoder::tOutput encoderData;
413
414 encoderData.value = __builtin_bswap32(dmaWord);
415 result = encoderData.Value;
416 }
417#endif
418
419 return result;
420}
421
422int32_t DMASample::Get(Encoder *input) const {
423 int32_t raw = GetRaw(input);
424
Brian Silvermand49fd782015-01-30 16:43:17 -0500425 return raw / input->GetEncodingScale();
426}
427
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800428uint16_t DMASample::GetValue(AnalogInput *input) const {
Brian Silvermand49fd782015-01-30 16:43:17 -0500429 if (offset(kEnable_Encoders) == -1) {
430 wpi_setStaticErrorWithContext(dma_,
431 NiFpga_Status_ResourceNotFound,
432 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800433 return 0xffff;
Brian Silvermand49fd782015-01-30 16:43:17 -0500434 }
435
436 uint32_t dmaWord;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800437 uint32_t channel = input->GetChannel();
Austin Schuhc6cc4102015-02-15 23:19:53 -0800438 if (channel <= 3) {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800439 dmaWord = read_buffer_[offset(kEnable_AI0_Low) + channel / 2];
Brian Silvermand49fd782015-01-30 16:43:17 -0500440 } else {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800441 dmaWord = read_buffer_[offset(kEnable_AI0_High) + (channel - 4) / 2];
Brian Silvermand49fd782015-01-30 16:43:17 -0500442 }
Austin Schuhc6cc4102015-02-15 23:19:53 -0800443 if (channel % 2) {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800444 return (dmaWord >> 16) & 0xffff;
445 } else {
446 return (dmaWord) & 0xffff;
447 }
Brian Silvermand49fd782015-01-30 16:43:17 -0500448 return static_cast<int16_t>(dmaWord);
449}
450
451float DMASample::GetVoltage(AnalogInput *input) const {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800452 uint16_t value = GetValue(input);
453 if (value == 0xffff) return 0.0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500454 uint32_t lsb_weight = input->GetLSBWeight();
455 int32_t offset = input->GetOffset();
456 float voltage = lsb_weight * 1.0e-9 * value - offset * 1.0e-9;
457 return voltage;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500458}