blob: f775ff5a87be5c7126f2762d201d7daab63b7eda [file] [log] [blame]
Brian Silverman2aa83d72015-01-24 18:03:11 -05001#include "dma.h"
2
3#include <algorithm>
Brian Silvermand49fd782015-01-30 16:43:17 -05004#include <type_traits>
5
6#include "DigitalSource.h"
7#include "AnalogInput.h"
8#include "Encoder.h"
9
10// Interface to the roboRIO FPGA's DMA features.
Brian Silverman2aa83d72015-01-24 18:03:11 -050011
12// Like tEncoder::tOutput with the bitfields reversed.
13typedef union {
14 struct {
15 unsigned Direction: 1;
16 signed Value: 31;
17 };
18 struct {
19 unsigned value: 32;
20 };
21} t1Output;
22
23static const uint32_t kNumHeaders = 10;
24
25static constexpr ssize_t kChannelSize[18] = {2, 2, 4, 4, 2, 2, 4, 4, 3,
26 3, 2, 1, 4, 4, 4, 4, 4, 4};
27
28enum DMAOffsetConstants {
29 kEnable_AI0_Low = 0,
30 kEnable_AI0_High = 1,
31 kEnable_AIAveraged0_Low = 2,
32 kEnable_AIAveraged0_High = 3,
33 kEnable_AI1_Low = 4,
34 kEnable_AI1_High = 5,
35 kEnable_AIAveraged1_Low = 6,
36 kEnable_AIAveraged1_High = 7,
37 kEnable_Accumulator0 = 8,
38 kEnable_Accumulator1 = 9,
39 kEnable_DI = 10,
40 kEnable_AnalogTriggers = 11,
41 kEnable_Counters_Low = 12,
42 kEnable_Counters_High = 13,
43 kEnable_CounterTimers_Low = 14,
44 kEnable_CounterTimers_High = 15,
45 kEnable_Encoders = 16,
46 kEnable_EncoderTimers = 17,
47};
48
49DMA::DMA() {
50 tRioStatusCode status = 0;
51 tdma_config_ = tDMA::create(&status);
52 wpi_setErrorWithContext(status, getHALErrorMessage(status));
53 if (status != 0) {
54 return;
55 }
56 SetRate(1);
57 SetPause(false);
58}
59
60DMA::~DMA() {
61 tRioStatusCode status = 0;
62
63 manager_->stop(&status);
64 delete tdma_config_;
65}
66
67void DMA::SetPause(bool pause) {
68 tRioStatusCode status = 0;
69 tdma_config_->writeConfig_Pause(pause, &status);
70 wpi_setErrorWithContext(status, getHALErrorMessage(status));
71}
72
73void DMA::SetRate(uint32_t cycles) {
74 if (cycles < 1) {
75 cycles = 1;
76 }
77 tRioStatusCode status = 0;
78 tdma_config_->writeRate(cycles, &status);
79 wpi_setErrorWithContext(status, getHALErrorMessage(status));
80}
81
82void DMA::Add(Encoder * /*encoder*/) {
83 tRioStatusCode status = 0;
84
85 if (manager_) {
86 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
87 "DMA::Add() only works before DMA::Start()");
88 return;
89 }
90
91 fprintf(stderr, "DMA::Add(Encoder*) needs re-testing. aborting\n");
92 abort();
93
94 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
95 tdma_config_->writeConfig_Enable_Encoders(true, &status);
96 wpi_setErrorWithContext(status, getHALErrorMessage(status));
97}
98
99void DMA::Add(DigitalSource * /*input*/) {
100 tRioStatusCode status = 0;
101
102 if (manager_) {
103 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
104 "DMA::Add() only works before DMA::Start()");
105 return;
106 }
107
108 tdma_config_->writeConfig_Enable_DI(true, &status);
109 wpi_setErrorWithContext(status, getHALErrorMessage(status));
110}
111
Brian Silvermand49fd782015-01-30 16:43:17 -0500112void DMA::Add(AnalogInput *input) {
113 tRioStatusCode status = 0;
114
115 if (manager_) {
116 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
117 "DMA::Add() only works before DMA::Start()");
118 return;
119 }
120
121 fprintf(stderr, "DMA::Add(AnalogInput*) needs testing. aborting\n");
122 abort();
123
124 // TODO(brian): Figure out if this math is actually correct.
125 if (input->GetChannel() <= 3) {
126 tdma_config_->writeConfig_Enable_AI0_Low(true, &status);
127 } else {
128 tdma_config_->writeConfig_Enable_AI0_High(true, &status);
129 }
130 wpi_setErrorWithContext(status, getHALErrorMessage(status));
131}
132
Brian Silverman2aa83d72015-01-24 18:03:11 -0500133void DMA::SetExternalTrigger(DigitalSource *input, bool rising, bool falling) {
134 tRioStatusCode status = 0;
135
136 if (manager_) {
137 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
138 "DMA::SetExternalTrigger() only works before DMA::Start()");
139 return;
140 }
141
142 auto index =
143 ::std::find(trigger_channels_.begin(), trigger_channels_.end(), false);
144 if (index == trigger_channels_.end()) {
145 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
146 "DMA: No channels left");
147 return;
148 }
149 *index = true;
150
151 const int channel_index = index - trigger_channels_.begin();
152
153 tdma_config_->writeConfig_ExternalClock(true, &status);
154 wpi_setErrorWithContext(status, getHALErrorMessage(status));
155 if (status != 0) {
156 return;
157 }
158
159 // Configures the trigger to be external, not off the FPGA clock.
160 tdma_config_->writeExternalTriggers_ExternalClockSource_Channel(
161 channel_index, input->GetChannelForRouting(), &status);
162 wpi_setErrorWithContext(status, getHALErrorMessage(status));
163 if (status != 0) {
164 return;
165 }
166
167 tdma_config_->writeExternalTriggers_ExternalClockSource_Module(
168 channel_index, input->GetModuleForRouting(), &status);
169 wpi_setErrorWithContext(status, getHALErrorMessage(status));
170 if (status != 0) {
171 return;
172 }
173 tdma_config_->writeExternalTriggers_ExternalClockSource_AnalogTrigger(
174 channel_index, input->GetAnalogTriggerForRouting(), &status);
175 wpi_setErrorWithContext(status, getHALErrorMessage(status));
176 if (status != 0) {
177 return;
178 }
179 tdma_config_->writeExternalTriggers_RisingEdge(channel_index, rising,
180 &status);
181 wpi_setErrorWithContext(status, getHALErrorMessage(status));
182 if (status != 0) {
183 return;
184 }
185 tdma_config_->writeExternalTriggers_FallingEdge(channel_index, falling,
186 &status);
187 wpi_setErrorWithContext(status, getHALErrorMessage(status));
188 if (status != 0) {
189 return;
190 }
191}
192
193DMA::ReadStatus DMA::Read(DMASample *sample, uint32_t timeout_ms,
Brian Silvermand49fd782015-01-30 16:43:17 -0500194 size_t *remaining_out) {
Brian Silverman2aa83d72015-01-24 18:03:11 -0500195 tRioStatusCode status = 0;
196 size_t remainingBytes = 0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500197 *remaining_out = 0;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500198
199 if (!manager_.get()) {
200 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
201 "DMA::Read() only works after DMA::Start()");
202 return STATUS_ERROR;
203 }
204
Brian Silvermand49fd782015-01-30 16:43:17 -0500205 sample->dma_ = this;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500206 // memset(&sample->read_buffer_, 0, sizeof(read_buffer_));
207 manager_->read(sample->read_buffer_, capture_size_, timeout_ms,
208 &remainingBytes, &status);
209
210 if (0) { // DEBUG
211 printf("buf[] = ");
212 for (size_t i = 0;
213 i < sizeof(sample->read_buffer_) / sizeof(sample->read_buffer_[0]);
214 ++i) {
215 if (i != 0) {
216 printf(" ");
217 }
218 printf("0x%.8x", sample->read_buffer_[i]);
219 }
220 printf("\n");
221 }
222
223 // TODO(jerry): Do this only if status == 0?
Brian Silvermand49fd782015-01-30 16:43:17 -0500224 *remaining_out = remainingBytes / capture_size_;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500225
226 if (0) { // DEBUG
Brian Silvermand49fd782015-01-30 16:43:17 -0500227 printf("Remaining samples = %d\n", *remaining_out);
Brian Silverman2aa83d72015-01-24 18:03:11 -0500228 }
229
230 // TODO(austin): Check that *remainingBytes % capture_size_ == 0 and deal
231 // with it if it isn't. Probably meant that we overflowed?
232 if (status == 0) {
233 return STATUS_OK;
234 } else if (status == NiFpga_Status_FifoTimeout) {
235 return STATUS_TIMEOUT;
236 } else {
237 wpi_setErrorWithContext(status, getHALErrorMessage(status));
238 return STATUS_ERROR;
239 }
240}
241
Brian Silvermand49fd782015-01-30 16:43:17 -0500242const char *DMA::NameOfReadStatus(ReadStatus s) {
243 switch (s) {
244 case STATUS_OK: return "OK";
245 case STATUS_TIMEOUT: return "TIMEOUT";
246 case STATUS_ERROR: return "ERROR";
247 default: return "(bad ReadStatus code)";
248 }
249}
250
Brian Silverman2aa83d72015-01-24 18:03:11 -0500251void DMA::Start(size_t queue_depth) {
252 tRioStatusCode status = 0;
253 tconfig_ = tdma_config_->readConfig(&status);
254 wpi_setErrorWithContext(status, getHALErrorMessage(status));
255 if (status != 0) {
256 return;
257 }
258
259 {
260 size_t accum_size = 0;
261#define SET_SIZE(bit) \
262 if (tconfig_.bit) { \
263 channel_offsets_[k##bit] = accum_size; \
264 accum_size += kChannelSize[k##bit]; \
265 } else { \
266 channel_offsets_[k##bit] = -1; \
267 }
268
269 SET_SIZE(Enable_AI0_Low);
270 SET_SIZE(Enable_AI0_High);
271 SET_SIZE(Enable_AIAveraged0_Low);
272 SET_SIZE(Enable_AIAveraged0_High);
273 SET_SIZE(Enable_AI1_Low);
274 SET_SIZE(Enable_AI1_High);
275 SET_SIZE(Enable_AIAveraged1_Low);
276 SET_SIZE(Enable_AIAveraged1_High);
277 SET_SIZE(Enable_Accumulator0);
278 SET_SIZE(Enable_Accumulator1);
279 SET_SIZE(Enable_DI);
280 SET_SIZE(Enable_AnalogTriggers);
281 SET_SIZE(Enable_Counters_Low);
282 SET_SIZE(Enable_Counters_High);
283 SET_SIZE(Enable_CounterTimers_Low);
284 SET_SIZE(Enable_CounterTimers_High);
285 SET_SIZE(Enable_Encoders);
286 SET_SIZE(Enable_EncoderTimers);
287#undef SET_SIZE
288 capture_size_ = accum_size + 1;
289 }
290
291 manager_.reset(new nFPGA::tDMAManager(0, queue_depth * capture_size_, &status));
292 wpi_setErrorWithContext(status, getHALErrorMessage(status));
293 if (status != 0) {
294 return;
295 }
296 // Start, stop, start to clear the buffer.
297 manager_->start(&status);
298 wpi_setErrorWithContext(status, getHALErrorMessage(status));
299 if (status != 0) {
300 return;
301 }
302 manager_->stop(&status);
303 wpi_setErrorWithContext(status, getHALErrorMessage(status));
304 if (status != 0) {
305 return;
306 }
307 manager_->start(&status);
308 wpi_setErrorWithContext(status, getHALErrorMessage(status));
309 if (status != 0) {
310 return;
311 }
312}
313
Brian Silvermand49fd782015-01-30 16:43:17 -0500314static_assert(::std::is_pod<DMASample>::value, "DMASample needs to be POD");
315
Brian Silverman2aa83d72015-01-24 18:03:11 -0500316ssize_t DMASample::offset(int index) const { return dma_->channel_offsets_[index]; }
317
318double DMASample::GetTimestamp() const {
319 return static_cast<double>(read_buffer_[dma_->capture_size_ - 1]) * 0.000001;
320}
321
322bool DMASample::Get(DigitalSource *input) const {
323 if (offset(kEnable_DI) == -1) {
324 wpi_setStaticErrorWithContext(dma_,
325 NiFpga_Status_ResourceNotFound,
326 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
327 return false;
328 }
329 if (input->GetChannelForRouting() < kNumHeaders) {
330 return (read_buffer_[offset(kEnable_DI)] >>
331 input->GetChannelForRouting()) &
332 0x1;
333 } else {
334 return (read_buffer_[offset(kEnable_DI)] >>
335 (input->GetChannelForRouting() + 6)) &
336 0x1;
337 }
338}
339
340int32_t DMASample::GetRaw(Encoder *input) const {
341 if (offset(kEnable_Encoders) == -1) {
342 wpi_setStaticErrorWithContext(dma_,
343 NiFpga_Status_ResourceNotFound,
344 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
345 return -1;
346 }
347
348 uint32_t dmaWord =
349 read_buffer_[offset(kEnable_Encoders) + input->GetFPGAIndex()];
350 int32_t result = 0;
351
352 if (1) {
353 // Extract the 31-bit signed tEncoder::tOutput Value using a struct with the
354 // reverse packed field order of tOutput. This gets Value from the high
355 // order 31 bits of output on little-endian ARM using gcc. This works
356 // even though C/C++ doesn't guarantee bitfield order.
357 t1Output output;
358
359 output.value = dmaWord;
360 result = output.Value;
361 } else if (1) {
362 // Extract the 31-bit signed tEncoder::tOutput Value using right-shift.
363 // This works even though C/C++ doesn't guarantee whether signed >> does
Brian Silvermand49fd782015-01-30 16:43:17 -0500364 // arithmetic or logical shift. (dmaWord / 2) would fix that but it rounds.
Brian Silverman2aa83d72015-01-24 18:03:11 -0500365 result = static_cast<int32_t>(dmaWord) >> 1;
366 }
367#if 0 // This approach was recommended but it doesn't return the right value.
368 else {
369 // Byte-reverse the DMA word (big-endian value from the FPGA) then extract
370 // the 31-bit tEncoder::tOutput. This does not return the right Value.
371 tEncoder::tOutput encoderData;
372
373 encoderData.value = __builtin_bswap32(dmaWord);
374 result = encoderData.Value;
375 }
376#endif
377
378 return result;
379}
380
381int32_t DMASample::Get(Encoder *input) const {
382 int32_t raw = GetRaw(input);
383
Brian Silvermand49fd782015-01-30 16:43:17 -0500384 return raw / input->GetEncodingScale();
385}
386
387int16_t DMASample::GetValue(AnalogInput *input) const {
388 if (offset(kEnable_Encoders) == -1) {
389 wpi_setStaticErrorWithContext(dma_,
390 NiFpga_Status_ResourceNotFound,
391 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
392 return -1;
393 }
394
395 uint32_t dmaWord;
396 // TODO(brian): Figure out if this math is actually correct.
397 if (input->GetChannel() <= 3) {
398 dmaWord = read_buffer_[offset(kEnable_AI0_Low) + input->GetChannel()];
399 } else {
400 dmaWord = read_buffer_[offset(kEnable_AI0_High) + input->GetChannel() - 4];
401 }
402 // TODO(brian): Verify that this is correct.
403 return static_cast<int16_t>(dmaWord);
404}
405
406float DMASample::GetVoltage(AnalogInput *input) const {
407 int16_t value = GetValue(input);
408 if (value == -1) return 0.0;
409 uint32_t lsb_weight = input->GetLSBWeight();
410 int32_t offset = input->GetOffset();
411 float voltage = lsb_weight * 1.0e-9 * value - offset * 1.0e-9;
412 return voltage;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500413}