blob: e3b88d0b00c334d9ea41a09a3d1ee553494abb14 [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
Austin Schuh91c75562015-12-20 22:23:10 -080028#ifdef WPILIB2015
29static constexpr ssize_t kChannelSize[18] = {2, 2, 4, 4, 2, 2, 4, 4, 3, 3,
30 2, 1, 4, 4, 4, 4, 4, 4};
31#else
32static constexpr ssize_t kChannelSize[20] = {2, 2, 4, 4, 2, 2, 4, 4, 3, 3,
33 2, 1, 4, 4, 4, 4, 4, 4, 4, 4};
34#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -050035
36enum DMAOffsetConstants {
37 kEnable_AI0_Low = 0,
38 kEnable_AI0_High = 1,
39 kEnable_AIAveraged0_Low = 2,
40 kEnable_AIAveraged0_High = 3,
41 kEnable_AI1_Low = 4,
42 kEnable_AI1_High = 5,
43 kEnable_AIAveraged1_Low = 6,
44 kEnable_AIAveraged1_High = 7,
45 kEnable_Accumulator0 = 8,
46 kEnable_Accumulator1 = 9,
47 kEnable_DI = 10,
48 kEnable_AnalogTriggers = 11,
49 kEnable_Counters_Low = 12,
50 kEnable_Counters_High = 13,
51 kEnable_CounterTimers_Low = 14,
52 kEnable_CounterTimers_High = 15,
Austin Schuh91c75562015-12-20 22:23:10 -080053#ifdef WPILIB2015
Brian Silverman2aa83d72015-01-24 18:03:11 -050054 kEnable_Encoders = 16,
55 kEnable_EncoderTimers = 17,
Austin Schuh91c75562015-12-20 22:23:10 -080056#else
57 kEnable_Encoders_Low = 16,
58 kEnable_Encoders_High = 17,
59 kEnable_EncoderTimers_Low = 18,
60 kEnable_EncoderTimers_High = 19,
61#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -050062};
63
64DMA::DMA() {
65 tRioStatusCode status = 0;
66 tdma_config_ = tDMA::create(&status);
Austin Schuh58d8cdf2015-02-15 21:04:42 -080067 tdma_config_->writeConfig_ExternalClock(false, &status);
Brian Silverman2aa83d72015-01-24 18:03:11 -050068 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Austin Schuhb19fddb2015-11-22 22:25:29 -080069#ifdef WPILIB2015
Austin Schuh58d8cdf2015-02-15 21:04:42 -080070 NiFpga_WriteU32(0x10000, 0x1832c, 0x0);
Austin Schuhb19fddb2015-11-22 22:25:29 -080071#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -050072 if (status != 0) {
73 return;
74 }
75 SetRate(1);
76 SetPause(false);
77}
78
79DMA::~DMA() {
80 tRioStatusCode status = 0;
81
82 manager_->stop(&status);
83 delete tdma_config_;
84}
85
86void DMA::SetPause(bool pause) {
87 tRioStatusCode status = 0;
88 tdma_config_->writeConfig_Pause(pause, &status);
89 wpi_setErrorWithContext(status, getHALErrorMessage(status));
90}
91
92void DMA::SetRate(uint32_t cycles) {
93 if (cycles < 1) {
94 cycles = 1;
95 }
96 tRioStatusCode status = 0;
97 tdma_config_->writeRate(cycles, &status);
98 wpi_setErrorWithContext(status, getHALErrorMessage(status));
99}
100
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800101void DMA::Add(Encoder *encoder) {
Brian Silverman2aa83d72015-01-24 18:03:11 -0500102 tRioStatusCode status = 0;
103
104 if (manager_) {
105 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
106 "DMA::Add() only works before DMA::Start()");
107 return;
108 }
Austin Schuh91c75562015-12-20 22:23:10 -0800109 const int index = encoder->GetFPGAIndex();
110
111#ifdef WPILIB2015
112 if (index < 4) {
113 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
114 tdma_config_->writeConfig_Enable_Encoders(true, &status);
115 } else {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800116 wpi_setErrorWithContext(
117 NiFpga_Status_InvalidParameter,
118 "FPGA encoder index is not in the 4 that get logged.");
Austin Schuh91c75562015-12-20 22:23:10 -0800119 return;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800120 }
Austin Schuh91c75562015-12-20 22:23:10 -0800121#else
122 if (index < 4) {
123 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
124 tdma_config_->writeConfig_Enable_Encoders_Low(true, &status);
125 } else if (index < 8) {
126 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
127 tdma_config_->writeConfig_Enable_Encoders_High(true, &status);
128 } else {
129 wpi_setErrorWithContext(
130 NiFpga_Status_InvalidParameter,
131 "FPGA encoder index is not in the 4 that get logged.");
132 return;
133 }
134#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -0500135
Brian Silverman2aa83d72015-01-24 18:03:11 -0500136 wpi_setErrorWithContext(status, getHALErrorMessage(status));
137}
138
139void DMA::Add(DigitalSource * /*input*/) {
140 tRioStatusCode status = 0;
141
142 if (manager_) {
143 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
144 "DMA::Add() only works before DMA::Start()");
145 return;
146 }
147
148 tdma_config_->writeConfig_Enable_DI(true, &status);
149 wpi_setErrorWithContext(status, getHALErrorMessage(status));
150}
151
Brian Silvermand49fd782015-01-30 16:43:17 -0500152void DMA::Add(AnalogInput *input) {
153 tRioStatusCode status = 0;
154
155 if (manager_) {
156 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
157 "DMA::Add() only works before DMA::Start()");
158 return;
159 }
160
Brian Silvermand49fd782015-01-30 16:43:17 -0500161 if (input->GetChannel() <= 3) {
162 tdma_config_->writeConfig_Enable_AI0_Low(true, &status);
163 } else {
164 tdma_config_->writeConfig_Enable_AI0_High(true, &status);
165 }
166 wpi_setErrorWithContext(status, getHALErrorMessage(status));
167}
168
Brian Silverman2aa83d72015-01-24 18:03:11 -0500169void DMA::SetExternalTrigger(DigitalSource *input, bool rising, bool falling) {
170 tRioStatusCode status = 0;
171
172 if (manager_) {
173 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
174 "DMA::SetExternalTrigger() only works before DMA::Start()");
175 return;
176 }
177
178 auto index =
179 ::std::find(trigger_channels_.begin(), trigger_channels_.end(), false);
180 if (index == trigger_channels_.end()) {
181 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800182 "DMA: No channels left");
Brian Silverman2aa83d72015-01-24 18:03:11 -0500183 return;
184 }
185 *index = true;
186
Austin Schuhb19fddb2015-11-22 22:25:29 -0800187 const int channel_index = ::std::distance(trigger_channels_.begin(), index);
Brian Silverman2aa83d72015-01-24 18:03:11 -0500188
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800189 const bool is_external_clock =
190 tdma_config_->readConfig_ExternalClock(&status);
191 if (status == 0) {
192 if (!is_external_clock) {
193 tdma_config_->writeConfig_ExternalClock(true, &status);
194 wpi_setErrorWithContext(status, getHALErrorMessage(status));
195 if (status != 0) {
196 return;
197 }
198 }
199 } else {
200 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500201 return;
202 }
203
Austin Schuh3b5b69b2015-10-31 18:55:47 -0700204 nFPGA::nRoboRIO_FPGANamespace::tDMA::tExternalTriggers new_trigger;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800205
206 new_trigger.FallingEdge = falling;
207 new_trigger.RisingEdge = rising;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800208 new_trigger.ExternalClockSource_AnalogTrigger = false;
Austin Schuh91c75562015-12-20 22:23:10 -0800209 unsigned char module = 0;
210 unsigned char channel = input->GetChannelForRouting();
211 if (channel >= kNumHeaders) {
212 module = 1;
213 channel -= kNumHeaders;
214 } else {
215 module = 0;
216 }
217
218 new_trigger.ExternalClockSource_Module = module;
219 new_trigger.ExternalClockSource_Channel = channel;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800220
Austin Schuhb19fddb2015-11-22 22:25:29 -0800221// Configures the trigger to be external, not off the FPGA clock.
222#ifndef WPILIB2015
Austin Schuh91c75562015-12-20 22:23:10 -0800223 tdma_config_->writeExternalTriggers(channel_index / 4, channel_index % 4,
224 new_trigger, &status);
225 if (status != 0) {
226 wpi_setErrorWithContext(status, getHALErrorMessage(status));
227 return;
228 }
Austin Schuhb19fddb2015-11-22 22:25:29 -0800229#else
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800230 uint32_t current_triggers;
Austin Schuhb19fddb2015-11-22 22:25:29 -0800231 tRioStatusCode register_status =
232 NiFpga_ReadU32(0x10000, 0x1832c, &current_triggers);
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800233 if (register_status != 0) {
234 wpi_setErrorWithContext(register_status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500235 return;
236 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800237 current_triggers = (current_triggers & ~(0xff << (channel_index * 8))) |
238 (new_trigger.value << (channel_index * 8));
239 register_status = NiFpga_WriteU32(0x10000, 0x1832c, current_triggers);
240 if (register_status != 0) {
241 wpi_setErrorWithContext(register_status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500242 return;
243 }
Austin Schuhb19fddb2015-11-22 22:25:29 -0800244#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -0500245}
246
247DMA::ReadStatus DMA::Read(DMASample *sample, uint32_t timeout_ms,
Brian Silvermand49fd782015-01-30 16:43:17 -0500248 size_t *remaining_out) {
Brian Silverman2aa83d72015-01-24 18:03:11 -0500249 tRioStatusCode status = 0;
250 size_t remainingBytes = 0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500251 *remaining_out = 0;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500252
253 if (!manager_.get()) {
254 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
255 "DMA::Read() only works after DMA::Start()");
256 return STATUS_ERROR;
257 }
258
Brian Silvermand49fd782015-01-30 16:43:17 -0500259 sample->dma_ = this;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500260 manager_->read(sample->read_buffer_, capture_size_, timeout_ms,
261 &remainingBytes, &status);
262
Brian Silverman2aa83d72015-01-24 18:03:11 -0500263 // TODO(jerry): Do this only if status == 0?
Brian Silvermand49fd782015-01-30 16:43:17 -0500264 *remaining_out = remainingBytes / capture_size_;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500265
Brian Silverman2aa83d72015-01-24 18:03:11 -0500266 // TODO(austin): Check that *remainingBytes % capture_size_ == 0 and deal
267 // with it if it isn't. Probably meant that we overflowed?
268 if (status == 0) {
269 return STATUS_OK;
270 } else if (status == NiFpga_Status_FifoTimeout) {
271 return STATUS_TIMEOUT;
272 } else {
273 wpi_setErrorWithContext(status, getHALErrorMessage(status));
274 return STATUS_ERROR;
275 }
276}
277
Brian Silvermand49fd782015-01-30 16:43:17 -0500278const char *DMA::NameOfReadStatus(ReadStatus s) {
279 switch (s) {
280 case STATUS_OK: return "OK";
281 case STATUS_TIMEOUT: return "TIMEOUT";
282 case STATUS_ERROR: return "ERROR";
283 default: return "(bad ReadStatus code)";
284 }
285}
286
Brian Silverman2aa83d72015-01-24 18:03:11 -0500287void DMA::Start(size_t queue_depth) {
288 tRioStatusCode status = 0;
289 tconfig_ = tdma_config_->readConfig(&status);
290 wpi_setErrorWithContext(status, getHALErrorMessage(status));
291 if (status != 0) {
292 return;
293 }
294
295 {
296 size_t accum_size = 0;
297#define SET_SIZE(bit) \
298 if (tconfig_.bit) { \
299 channel_offsets_[k##bit] = accum_size; \
300 accum_size += kChannelSize[k##bit]; \
301 } else { \
302 channel_offsets_[k##bit] = -1; \
303 }
304
305 SET_SIZE(Enable_AI0_Low);
306 SET_SIZE(Enable_AI0_High);
307 SET_SIZE(Enable_AIAveraged0_Low);
308 SET_SIZE(Enable_AIAveraged0_High);
309 SET_SIZE(Enable_AI1_Low);
310 SET_SIZE(Enable_AI1_High);
311 SET_SIZE(Enable_AIAveraged1_Low);
312 SET_SIZE(Enable_AIAveraged1_High);
313 SET_SIZE(Enable_Accumulator0);
314 SET_SIZE(Enable_Accumulator1);
315 SET_SIZE(Enable_DI);
316 SET_SIZE(Enable_AnalogTriggers);
317 SET_SIZE(Enable_Counters_Low);
318 SET_SIZE(Enable_Counters_High);
319 SET_SIZE(Enable_CounterTimers_Low);
320 SET_SIZE(Enable_CounterTimers_High);
Austin Schuh91c75562015-12-20 22:23:10 -0800321#ifdef WPILIB2015
Brian Silverman2aa83d72015-01-24 18:03:11 -0500322 SET_SIZE(Enable_Encoders);
323 SET_SIZE(Enable_EncoderTimers);
Austin Schuh91c75562015-12-20 22:23:10 -0800324#else
325 SET_SIZE(Enable_Encoders_Low);
326 SET_SIZE(Enable_Encoders_High);
327 SET_SIZE(Enable_EncoderTimers_Low);
328 SET_SIZE(Enable_EncoderTimers_High);
329#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -0500330#undef SET_SIZE
331 capture_size_ = accum_size + 1;
332 }
333
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800334 manager_.reset(
335 new nFPGA::tDMAManager(0, queue_depth * capture_size_, &status));
336
Brian Silverman2aa83d72015-01-24 18:03:11 -0500337 wpi_setErrorWithContext(status, getHALErrorMessage(status));
338 if (status != 0) {
339 return;
340 }
341 // Start, stop, start to clear the buffer.
342 manager_->start(&status);
343 wpi_setErrorWithContext(status, getHALErrorMessage(status));
344 if (status != 0) {
345 return;
346 }
347 manager_->stop(&status);
348 wpi_setErrorWithContext(status, getHALErrorMessage(status));
349 if (status != 0) {
350 return;
351 }
352 manager_->start(&status);
353 wpi_setErrorWithContext(status, getHALErrorMessage(status));
354 if (status != 0) {
355 return;
356 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800357
Brian Silverman2aa83d72015-01-24 18:03:11 -0500358}
359
Brian Silvermand49fd782015-01-30 16:43:17 -0500360static_assert(::std::is_pod<DMASample>::value, "DMASample needs to be POD");
361
Brian Silverman2aa83d72015-01-24 18:03:11 -0500362ssize_t DMASample::offset(int index) const { return dma_->channel_offsets_[index]; }
363
Austin Schuh8f314a92015-11-22 21:35:40 -0800364uint32_t DMASample::GetTime() const {
365 return read_buffer_[dma_->capture_size_ - 1];
366}
367
Brian Silverman2aa83d72015-01-24 18:03:11 -0500368double DMASample::GetTimestamp() const {
Austin Schuh8f314a92015-11-22 21:35:40 -0800369 return static_cast<double>(GetTime()) * 0.000001;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500370}
371
372bool DMASample::Get(DigitalSource *input) const {
373 if (offset(kEnable_DI) == -1) {
Austin Schuh91c75562015-12-20 22:23:10 -0800374 wpi_setStaticErrorWithContext(
375 dma_, NiFpga_Status_ResourceNotFound,
Brian Silverman2aa83d72015-01-24 18:03:11 -0500376 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
377 return false;
378 }
379 if (input->GetChannelForRouting() < kNumHeaders) {
Austin Schuh91c75562015-12-20 22:23:10 -0800380 return (read_buffer_[offset(kEnable_DI)] >> input->GetChannelForRouting()) &
Brian Silverman2aa83d72015-01-24 18:03:11 -0500381 0x1;
382 } else {
383 return (read_buffer_[offset(kEnable_DI)] >>
384 (input->GetChannelForRouting() + 6)) &
385 0x1;
386 }
387}
388
389int32_t DMASample::GetRaw(Encoder *input) const {
Austin Schuh91c75562015-12-20 22:23:10 -0800390 int index = input->GetFPGAIndex();
391 uint32_t dmaWord = 0;
392#ifdef WPILIB2015
393 if (index >= 4 || offset(kEnable_Encoders) == -1) {
394 wpi_setStaticErrorWithContext(
395 dma_, NiFpga_Status_ResourceNotFound,
Brian Silverman2aa83d72015-01-24 18:03:11 -0500396 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
397 return -1;
398 }
Austin Schuh91c75562015-12-20 22:23:10 -0800399 dmaWord = read_buffer_[offset(kEnable_Encoders) + index];
400#else
401 if (index < 4) {
402 if (offset(kEnable_Encoders_Low) == -1) {
403 wpi_setStaticErrorWithContext(
404 dma_, NiFpga_Status_ResourceNotFound,
405 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
406 return -1;
407 }
408 dmaWord = read_buffer_[offset(kEnable_Encoders_Low) + index];
409 } else if (index < 8) {
410 if (offset(kEnable_Encoders_High) == -1) {
411 wpi_setStaticErrorWithContext(
412 dma_, NiFpga_Status_ResourceNotFound,
413 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
414 return -1;
415 }
416 dmaWord = read_buffer_[offset(kEnable_Encoders_High) + (index - 4)];
417 } else {
418 wpi_setStaticErrorWithContext(
419 dma_, NiFpga_Status_ResourceNotFound,
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800420 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
Austin Schuh91c75562015-12-20 22:23:10 -0800421 return 0;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800422 }
Austin Schuh91c75562015-12-20 22:23:10 -0800423#endif
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800424
Brian Silverman2aa83d72015-01-24 18:03:11 -0500425 int32_t result = 0;
426
Austin Schuhb19fddb2015-11-22 22:25:29 -0800427 // Extract the 31-bit signed tEncoder::tOutput Value using a struct with the
428 // reverse packed field order of tOutput. This gets Value from the high
429 // order 31 bits of output on little-endian ARM using gcc. This works
430 // even though C/C++ doesn't guarantee bitfield order.
431 t1Output output;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500432
Austin Schuhb19fddb2015-11-22 22:25:29 -0800433 output.value = dmaWord;
434 result = output.Value;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500435
436 return result;
437}
438
439int32_t DMASample::Get(Encoder *input) const {
440 int32_t raw = GetRaw(input);
441
Brian Silvermand49fd782015-01-30 16:43:17 -0500442 return raw / input->GetEncodingScale();
443}
444
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800445uint16_t DMASample::GetValue(AnalogInput *input) const {
Austin Schuh91c75562015-12-20 22:23:10 -0800446 uint32_t channel = input->GetChannel();
447 uint32_t dmaWord;
448 if (channel < 4) {
449 if (offset(kEnable_AI0_Low) == -1) {
450 wpi_setStaticErrorWithContext(
451 dma_, NiFpga_Status_ResourceNotFound,
452 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
453 return 0xffff;
454 }
455 dmaWord = read_buffer_[offset(kEnable_AI0_Low) + channel / 2];
456 } else if (channel < 8) {
457 if (offset(kEnable_AI0_High) == -1) {
458 wpi_setStaticErrorWithContext(
459 dma_, NiFpga_Status_ResourceNotFound,
460 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
461 return 0xffff;
462 }
463 dmaWord = read_buffer_[offset(kEnable_AI0_High) + (channel - 4) / 2];
464 } else {
465 wpi_setStaticErrorWithContext(
466 dma_, NiFpga_Status_ResourceNotFound,
Brian Silvermand49fd782015-01-30 16:43:17 -0500467 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800468 return 0xffff;
Brian Silvermand49fd782015-01-30 16:43:17 -0500469 }
Austin Schuhc6cc4102015-02-15 23:19:53 -0800470 if (channel % 2) {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800471 return (dmaWord >> 16) & 0xffff;
472 } else {
Austin Schuh91c75562015-12-20 22:23:10 -0800473 return dmaWord & 0xffff;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800474 }
Brian Silvermand49fd782015-01-30 16:43:17 -0500475 return static_cast<int16_t>(dmaWord);
476}
477
478float DMASample::GetVoltage(AnalogInput *input) const {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800479 uint16_t value = GetValue(input);
480 if (value == 0xffff) return 0.0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500481 uint32_t lsb_weight = input->GetLSBWeight();
482 int32_t offset = input->GetOffset();
483 float voltage = lsb_weight * 1.0e-9 * value - offset * 1.0e-9;
484 return voltage;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500485}