blob: 49714f29c8b2d7717d0612953bdfbac62e3b1964 [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 Schuhb19fddb2015-11-22 22:25:29 -080057#ifdef WPILIB2015
Austin Schuh58d8cdf2015-02-15 21:04:42 -080058 NiFpga_WriteU32(0x10000, 0x1832c, 0x0);
Austin Schuhb19fddb2015-11-22 22:25:29 -080059#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -050060 if (status != 0) {
61 return;
62 }
63 SetRate(1);
64 SetPause(false);
65}
66
67DMA::~DMA() {
68 tRioStatusCode status = 0;
69
70 manager_->stop(&status);
71 delete tdma_config_;
72}
73
74void DMA::SetPause(bool pause) {
75 tRioStatusCode status = 0;
76 tdma_config_->writeConfig_Pause(pause, &status);
77 wpi_setErrorWithContext(status, getHALErrorMessage(status));
78}
79
80void DMA::SetRate(uint32_t cycles) {
81 if (cycles < 1) {
82 cycles = 1;
83 }
84 tRioStatusCode status = 0;
85 tdma_config_->writeRate(cycles, &status);
86 wpi_setErrorWithContext(status, getHALErrorMessage(status));
87}
88
Austin Schuh58d8cdf2015-02-15 21:04:42 -080089void DMA::Add(Encoder *encoder) {
Brian Silverman2aa83d72015-01-24 18:03:11 -050090 tRioStatusCode status = 0;
91
92 if (manager_) {
93 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
94 "DMA::Add() only works before DMA::Start()");
95 return;
96 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -080097 if (encoder->GetFPGAIndex() >= 4) {
98 wpi_setErrorWithContext(
99 NiFpga_Status_InvalidParameter,
100 "FPGA encoder index is not in the 4 that get logged.");
101 }
Brian Silverman2aa83d72015-01-24 18:03:11 -0500102
103 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
104 tdma_config_->writeConfig_Enable_Encoders(true, &status);
105 wpi_setErrorWithContext(status, getHALErrorMessage(status));
106}
107
108void DMA::Add(DigitalSource * /*input*/) {
109 tRioStatusCode status = 0;
110
111 if (manager_) {
112 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
113 "DMA::Add() only works before DMA::Start()");
114 return;
115 }
116
117 tdma_config_->writeConfig_Enable_DI(true, &status);
118 wpi_setErrorWithContext(status, getHALErrorMessage(status));
119}
120
Brian Silvermand49fd782015-01-30 16:43:17 -0500121void DMA::Add(AnalogInput *input) {
122 tRioStatusCode status = 0;
123
124 if (manager_) {
125 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
126 "DMA::Add() only works before DMA::Start()");
127 return;
128 }
129
Brian Silvermand49fd782015-01-30 16:43:17 -0500130 if (input->GetChannel() <= 3) {
131 tdma_config_->writeConfig_Enable_AI0_Low(true, &status);
132 } else {
133 tdma_config_->writeConfig_Enable_AI0_High(true, &status);
134 }
135 wpi_setErrorWithContext(status, getHALErrorMessage(status));
136}
137
Brian Silverman2aa83d72015-01-24 18:03:11 -0500138void DMA::SetExternalTrigger(DigitalSource *input, bool rising, bool falling) {
139 tRioStatusCode status = 0;
140
141 if (manager_) {
142 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
143 "DMA::SetExternalTrigger() only works before DMA::Start()");
144 return;
145 }
146
147 auto index =
148 ::std::find(trigger_channels_.begin(), trigger_channels_.end(), false);
149 if (index == trigger_channels_.end()) {
150 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800151 "DMA: No channels left");
Brian Silverman2aa83d72015-01-24 18:03:11 -0500152 return;
153 }
154 *index = true;
155
Austin Schuhb19fddb2015-11-22 22:25:29 -0800156 const int channel_index = ::std::distance(trigger_channels_.begin(), index);
Brian Silverman2aa83d72015-01-24 18:03:11 -0500157
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800158 const bool is_external_clock =
159 tdma_config_->readConfig_ExternalClock(&status);
160 if (status == 0) {
161 if (!is_external_clock) {
162 tdma_config_->writeConfig_ExternalClock(true, &status);
163 wpi_setErrorWithContext(status, getHALErrorMessage(status));
164 if (status != 0) {
165 return;
166 }
167 }
168 } else {
169 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500170 return;
171 }
172
Austin Schuh3b5b69b2015-10-31 18:55:47 -0700173 nFPGA::nRoboRIO_FPGANamespace::tDMA::tExternalTriggers new_trigger;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800174
175 new_trigger.FallingEdge = falling;
176 new_trigger.RisingEdge = rising;
177 new_trigger.ExternalClockSource_AnalogTrigger =
178 input->GetAnalogTriggerForRouting();
179 new_trigger.ExternalClockSource_AnalogTrigger = false;
180 new_trigger.ExternalClockSource_Module = input->GetModuleForRouting();
181 new_trigger.ExternalClockSource_Channel = input->GetChannelForRouting();
182
Austin Schuhb19fddb2015-11-22 22:25:29 -0800183// Configures the trigger to be external, not off the FPGA clock.
184#ifndef WPILIB2015
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800185 tdma_config_->writeExternalTriggers(channel_index, new_trigger, &status);
Brian Silverman2aa83d72015-01-24 18:03:11 -0500186 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Austin Schuhb19fddb2015-11-22 22:25:29 -0800187#else
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800188 uint32_t current_triggers;
Austin Schuhb19fddb2015-11-22 22:25:29 -0800189 tRioStatusCode register_status =
190 NiFpga_ReadU32(0x10000, 0x1832c, &current_triggers);
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800191 if (register_status != 0) {
192 wpi_setErrorWithContext(register_status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500193 return;
194 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800195 current_triggers = (current_triggers & ~(0xff << (channel_index * 8))) |
196 (new_trigger.value << (channel_index * 8));
197 register_status = NiFpga_WriteU32(0x10000, 0x1832c, current_triggers);
198 if (register_status != 0) {
199 wpi_setErrorWithContext(register_status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500200 return;
201 }
Austin Schuhb19fddb2015-11-22 22:25:29 -0800202#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -0500203}
204
205DMA::ReadStatus DMA::Read(DMASample *sample, uint32_t timeout_ms,
Brian Silvermand49fd782015-01-30 16:43:17 -0500206 size_t *remaining_out) {
Brian Silverman2aa83d72015-01-24 18:03:11 -0500207 tRioStatusCode status = 0;
208 size_t remainingBytes = 0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500209 *remaining_out = 0;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500210
211 if (!manager_.get()) {
212 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
213 "DMA::Read() only works after DMA::Start()");
214 return STATUS_ERROR;
215 }
216
Brian Silvermand49fd782015-01-30 16:43:17 -0500217 sample->dma_ = this;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500218 manager_->read(sample->read_buffer_, capture_size_, timeout_ms,
219 &remainingBytes, &status);
220
Brian Silverman2aa83d72015-01-24 18:03:11 -0500221 // TODO(jerry): Do this only if status == 0?
Brian Silvermand49fd782015-01-30 16:43:17 -0500222 *remaining_out = remainingBytes / capture_size_;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500223
Brian Silverman2aa83d72015-01-24 18:03:11 -0500224 // TODO(austin): Check that *remainingBytes % capture_size_ == 0 and deal
225 // with it if it isn't. Probably meant that we overflowed?
226 if (status == 0) {
227 return STATUS_OK;
228 } else if (status == NiFpga_Status_FifoTimeout) {
229 return STATUS_TIMEOUT;
230 } else {
231 wpi_setErrorWithContext(status, getHALErrorMessage(status));
232 return STATUS_ERROR;
233 }
234}
235
Brian Silvermand49fd782015-01-30 16:43:17 -0500236const char *DMA::NameOfReadStatus(ReadStatus s) {
237 switch (s) {
238 case STATUS_OK: return "OK";
239 case STATUS_TIMEOUT: return "TIMEOUT";
240 case STATUS_ERROR: return "ERROR";
241 default: return "(bad ReadStatus code)";
242 }
243}
244
Brian Silverman2aa83d72015-01-24 18:03:11 -0500245void DMA::Start(size_t queue_depth) {
246 tRioStatusCode status = 0;
247 tconfig_ = tdma_config_->readConfig(&status);
248 wpi_setErrorWithContext(status, getHALErrorMessage(status));
249 if (status != 0) {
250 return;
251 }
252
253 {
254 size_t accum_size = 0;
255#define SET_SIZE(bit) \
256 if (tconfig_.bit) { \
257 channel_offsets_[k##bit] = accum_size; \
258 accum_size += kChannelSize[k##bit]; \
259 } else { \
260 channel_offsets_[k##bit] = -1; \
261 }
262
263 SET_SIZE(Enable_AI0_Low);
264 SET_SIZE(Enable_AI0_High);
265 SET_SIZE(Enable_AIAveraged0_Low);
266 SET_SIZE(Enable_AIAveraged0_High);
267 SET_SIZE(Enable_AI1_Low);
268 SET_SIZE(Enable_AI1_High);
269 SET_SIZE(Enable_AIAveraged1_Low);
270 SET_SIZE(Enable_AIAveraged1_High);
271 SET_SIZE(Enable_Accumulator0);
272 SET_SIZE(Enable_Accumulator1);
273 SET_SIZE(Enable_DI);
274 SET_SIZE(Enable_AnalogTriggers);
275 SET_SIZE(Enable_Counters_Low);
276 SET_SIZE(Enable_Counters_High);
277 SET_SIZE(Enable_CounterTimers_Low);
278 SET_SIZE(Enable_CounterTimers_High);
279 SET_SIZE(Enable_Encoders);
280 SET_SIZE(Enable_EncoderTimers);
281#undef SET_SIZE
282 capture_size_ = accum_size + 1;
283 }
284
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800285 manager_.reset(
286 new nFPGA::tDMAManager(0, queue_depth * capture_size_, &status));
287
Brian Silverman2aa83d72015-01-24 18:03:11 -0500288 wpi_setErrorWithContext(status, getHALErrorMessage(status));
289 if (status != 0) {
290 return;
291 }
292 // Start, stop, start to clear the buffer.
293 manager_->start(&status);
294 wpi_setErrorWithContext(status, getHALErrorMessage(status));
295 if (status != 0) {
296 return;
297 }
298 manager_->stop(&status);
299 wpi_setErrorWithContext(status, getHALErrorMessage(status));
300 if (status != 0) {
301 return;
302 }
303 manager_->start(&status);
304 wpi_setErrorWithContext(status, getHALErrorMessage(status));
305 if (status != 0) {
306 return;
307 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800308
Brian Silverman2aa83d72015-01-24 18:03:11 -0500309}
310
Brian Silvermand49fd782015-01-30 16:43:17 -0500311static_assert(::std::is_pod<DMASample>::value, "DMASample needs to be POD");
312
Brian Silverman2aa83d72015-01-24 18:03:11 -0500313ssize_t DMASample::offset(int index) const { return dma_->channel_offsets_[index]; }
314
Austin Schuh8f314a92015-11-22 21:35:40 -0800315uint32_t DMASample::GetTime() const {
316 return read_buffer_[dma_->capture_size_ - 1];
317}
318
Brian Silverman2aa83d72015-01-24 18:03:11 -0500319double DMASample::GetTimestamp() const {
Austin Schuh8f314a92015-11-22 21:35:40 -0800320 return static_cast<double>(GetTime()) * 0.000001;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500321}
322
323bool DMASample::Get(DigitalSource *input) const {
324 if (offset(kEnable_DI) == -1) {
325 wpi_setStaticErrorWithContext(dma_,
326 NiFpga_Status_ResourceNotFound,
327 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
328 return false;
329 }
330 if (input->GetChannelForRouting() < kNumHeaders) {
331 return (read_buffer_[offset(kEnable_DI)] >>
332 input->GetChannelForRouting()) &
333 0x1;
334 } else {
335 return (read_buffer_[offset(kEnable_DI)] >>
336 (input->GetChannelForRouting() + 6)) &
337 0x1;
338 }
339}
340
341int32_t DMASample::GetRaw(Encoder *input) const {
342 if (offset(kEnable_Encoders) == -1) {
343 wpi_setStaticErrorWithContext(dma_,
344 NiFpga_Status_ResourceNotFound,
345 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
346 return -1;
347 }
348
Austin Schuhc6cc4102015-02-15 23:19:53 -0800349 if (input->GetFPGAIndex() >= 4) {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800350 wpi_setStaticErrorWithContext(dma_,
351 NiFpga_Status_ResourceNotFound,
352 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
353 }
354
Brian Silverman2aa83d72015-01-24 18:03:11 -0500355 uint32_t dmaWord =
356 read_buffer_[offset(kEnable_Encoders) + input->GetFPGAIndex()];
357 int32_t result = 0;
358
Austin Schuhb19fddb2015-11-22 22:25:29 -0800359 // Extract the 31-bit signed tEncoder::tOutput Value using a struct with the
360 // reverse packed field order of tOutput. This gets Value from the high
361 // order 31 bits of output on little-endian ARM using gcc. This works
362 // even though C/C++ doesn't guarantee bitfield order.
363 t1Output output;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500364
Austin Schuhb19fddb2015-11-22 22:25:29 -0800365 output.value = dmaWord;
366 result = output.Value;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500367
368 return result;
369}
370
371int32_t DMASample::Get(Encoder *input) const {
372 int32_t raw = GetRaw(input);
373
Brian Silvermand49fd782015-01-30 16:43:17 -0500374 return raw / input->GetEncodingScale();
375}
376
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800377uint16_t DMASample::GetValue(AnalogInput *input) const {
Brian Silvermand49fd782015-01-30 16:43:17 -0500378 if (offset(kEnable_Encoders) == -1) {
379 wpi_setStaticErrorWithContext(dma_,
380 NiFpga_Status_ResourceNotFound,
381 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800382 return 0xffff;
Brian Silvermand49fd782015-01-30 16:43:17 -0500383 }
384
385 uint32_t dmaWord;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800386 uint32_t channel = input->GetChannel();
Austin Schuhc6cc4102015-02-15 23:19:53 -0800387 if (channel <= 3) {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800388 dmaWord = read_buffer_[offset(kEnable_AI0_Low) + channel / 2];
Brian Silvermand49fd782015-01-30 16:43:17 -0500389 } else {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800390 dmaWord = read_buffer_[offset(kEnable_AI0_High) + (channel - 4) / 2];
Brian Silvermand49fd782015-01-30 16:43:17 -0500391 }
Austin Schuhc6cc4102015-02-15 23:19:53 -0800392 if (channel % 2) {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800393 return (dmaWord >> 16) & 0xffff;
394 } else {
395 return (dmaWord) & 0xffff;
396 }
Brian Silvermand49fd782015-01-30 16:43:17 -0500397 return static_cast<int16_t>(dmaWord);
398}
399
400float DMASample::GetVoltage(AnalogInput *input) const {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800401 uint16_t value = GetValue(input);
402 if (value == 0xffff) return 0.0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500403 uint32_t lsb_weight = input->GetLSBWeight();
404 int32_t offset = input->GetOffset();
405 float voltage = lsb_weight * 1.0e-9 * value - offset * 1.0e-9;
406 return voltage;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500407}