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