blob: ea9f64c56b98c2e8b9902a890d471e011373b081 [file] [log] [blame]
Brian Silvermanb5b46ca2016-03-13 01:14:17 -05001#include "frc971/wpilib/dma.h"
Brian Silverman2aa83d72015-01-24 18:03:11 -05002
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
12// Interface to the roboRIO FPGA's DMA features.
Brian Silverman2aa83d72015-01-24 18:03:11 -050013
14// Like tEncoder::tOutput with the bitfields reversed.
15typedef union {
16 struct {
17 unsigned Direction: 1;
18 signed Value: 31;
19 };
20 struct {
21 unsigned value: 32;
22 };
23} t1Output;
24
25static const uint32_t kNumHeaders = 10;
26
Austin Schuh91c75562015-12-20 22:23:10 -080027#ifdef WPILIB2015
28static constexpr ssize_t kChannelSize[18] = {2, 2, 4, 4, 2, 2, 4, 4, 3, 3,
29 2, 1, 4, 4, 4, 4, 4, 4};
30#else
31static constexpr ssize_t kChannelSize[20] = {2, 2, 4, 4, 2, 2, 4, 4, 3, 3,
32 2, 1, 4, 4, 4, 4, 4, 4, 4, 4};
33#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -050034
35enum DMAOffsetConstants {
36 kEnable_AI0_Low = 0,
37 kEnable_AI0_High = 1,
38 kEnable_AIAveraged0_Low = 2,
39 kEnable_AIAveraged0_High = 3,
40 kEnable_AI1_Low = 4,
41 kEnable_AI1_High = 5,
42 kEnable_AIAveraged1_Low = 6,
43 kEnable_AIAveraged1_High = 7,
44 kEnable_Accumulator0 = 8,
45 kEnable_Accumulator1 = 9,
46 kEnable_DI = 10,
47 kEnable_AnalogTriggers = 11,
48 kEnable_Counters_Low = 12,
49 kEnable_Counters_High = 13,
50 kEnable_CounterTimers_Low = 14,
51 kEnable_CounterTimers_High = 15,
Austin Schuh91c75562015-12-20 22:23:10 -080052#ifdef WPILIB2015
Brian Silverman2aa83d72015-01-24 18:03:11 -050053 kEnable_Encoders = 16,
54 kEnable_EncoderTimers = 17,
Austin Schuh91c75562015-12-20 22:23:10 -080055#else
56 kEnable_Encoders_Low = 16,
57 kEnable_Encoders_High = 17,
58 kEnable_EncoderTimers_Low = 18,
59 kEnable_EncoderTimers_High = 19,
60#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -050061};
62
63DMA::DMA() {
64 tRioStatusCode status = 0;
65 tdma_config_ = tDMA::create(&status);
Austin Schuh58d8cdf2015-02-15 21:04:42 -080066 tdma_config_->writeConfig_ExternalClock(false, &status);
Brian Silverman2aa83d72015-01-24 18:03:11 -050067 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Austin Schuhb19fddb2015-11-22 22:25:29 -080068#ifdef WPILIB2015
Austin Schuh58d8cdf2015-02-15 21:04:42 -080069 NiFpga_WriteU32(0x10000, 0x1832c, 0x0);
Austin Schuhb19fddb2015-11-22 22:25:29 -080070#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -050071 if (status != 0) {
72 return;
73 }
74 SetRate(1);
75 SetPause(false);
76}
77
78DMA::~DMA() {
79 tRioStatusCode status = 0;
80
81 manager_->stop(&status);
82 delete tdma_config_;
83}
84
85void DMA::SetPause(bool pause) {
86 tRioStatusCode status = 0;
87 tdma_config_->writeConfig_Pause(pause, &status);
88 wpi_setErrorWithContext(status, getHALErrorMessage(status));
89}
90
91void DMA::SetRate(uint32_t cycles) {
92 if (cycles < 1) {
93 cycles = 1;
94 }
95 tRioStatusCode status = 0;
96 tdma_config_->writeRate(cycles, &status);
97 wpi_setErrorWithContext(status, getHALErrorMessage(status));
98}
99
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800100void DMA::Add(Encoder *encoder) {
Brian Silverman2aa83d72015-01-24 18:03:11 -0500101 tRioStatusCode status = 0;
102
103 if (manager_) {
104 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
105 "DMA::Add() only works before DMA::Start()");
106 return;
107 }
Austin Schuh91c75562015-12-20 22:23:10 -0800108 const int index = encoder->GetFPGAIndex();
109
110#ifdef WPILIB2015
111 if (index < 4) {
112 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
113 tdma_config_->writeConfig_Enable_Encoders(true, &status);
114 } else {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800115 wpi_setErrorWithContext(
116 NiFpga_Status_InvalidParameter,
117 "FPGA encoder index is not in the 4 that get logged.");
Austin Schuh91c75562015-12-20 22:23:10 -0800118 return;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800119 }
Austin Schuh91c75562015-12-20 22:23:10 -0800120#else
121 if (index < 4) {
122 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
123 tdma_config_->writeConfig_Enable_Encoders_Low(true, &status);
124 } else if (index < 8) {
125 // TODO(austin): Encoder uses a Counter for 1x or 2x; quad for 4x...
126 tdma_config_->writeConfig_Enable_Encoders_High(true, &status);
127 } else {
128 wpi_setErrorWithContext(
129 NiFpga_Status_InvalidParameter,
130 "FPGA encoder index is not in the 4 that get logged.");
131 return;
132 }
133#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -0500134
Brian Silverman2aa83d72015-01-24 18:03:11 -0500135 wpi_setErrorWithContext(status, getHALErrorMessage(status));
136}
137
138void DMA::Add(DigitalSource * /*input*/) {
139 tRioStatusCode status = 0;
140
141 if (manager_) {
142 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
143 "DMA::Add() only works before DMA::Start()");
144 return;
145 }
146
147 tdma_config_->writeConfig_Enable_DI(true, &status);
148 wpi_setErrorWithContext(status, getHALErrorMessage(status));
149}
150
Brian Silvermand49fd782015-01-30 16:43:17 -0500151void DMA::Add(AnalogInput *input) {
152 tRioStatusCode status = 0;
153
154 if (manager_) {
155 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
156 "DMA::Add() only works before DMA::Start()");
157 return;
158 }
159
Brian Silvermand49fd782015-01-30 16:43:17 -0500160 if (input->GetChannel() <= 3) {
161 tdma_config_->writeConfig_Enable_AI0_Low(true, &status);
162 } else {
163 tdma_config_->writeConfig_Enable_AI0_High(true, &status);
164 }
165 wpi_setErrorWithContext(status, getHALErrorMessage(status));
166}
167
Brian Silverman2aa83d72015-01-24 18:03:11 -0500168void DMA::SetExternalTrigger(DigitalSource *input, bool rising, bool falling) {
169 tRioStatusCode status = 0;
170
171 if (manager_) {
172 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
173 "DMA::SetExternalTrigger() only works before DMA::Start()");
174 return;
175 }
176
177 auto index =
178 ::std::find(trigger_channels_.begin(), trigger_channels_.end(), false);
179 if (index == trigger_channels_.end()) {
180 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800181 "DMA: No channels left");
Brian Silverman2aa83d72015-01-24 18:03:11 -0500182 return;
183 }
184 *index = true;
185
Austin Schuhb19fddb2015-11-22 22:25:29 -0800186 const int channel_index = ::std::distance(trigger_channels_.begin(), index);
Brian Silverman2aa83d72015-01-24 18:03:11 -0500187
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800188 const bool is_external_clock =
189 tdma_config_->readConfig_ExternalClock(&status);
190 if (status == 0) {
191 if (!is_external_clock) {
192 tdma_config_->writeConfig_ExternalClock(true, &status);
193 wpi_setErrorWithContext(status, getHALErrorMessage(status));
194 if (status != 0) {
195 return;
196 }
197 }
198 } else {
199 wpi_setErrorWithContext(status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500200 return;
201 }
202
Austin Schuh3b5b69b2015-10-31 18:55:47 -0700203 nFPGA::nRoboRIO_FPGANamespace::tDMA::tExternalTriggers new_trigger;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800204
205 new_trigger.FallingEdge = falling;
206 new_trigger.RisingEdge = rising;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800207 new_trigger.ExternalClockSource_AnalogTrigger = false;
Austin Schuh91c75562015-12-20 22:23:10 -0800208 unsigned char module = 0;
209 unsigned char channel = input->GetChannelForRouting();
210 if (channel >= kNumHeaders) {
211 module = 1;
212 channel -= kNumHeaders;
213 } else {
214 module = 0;
215 }
216
217 new_trigger.ExternalClockSource_Module = module;
218 new_trigger.ExternalClockSource_Channel = channel;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800219
Austin Schuhb19fddb2015-11-22 22:25:29 -0800220// Configures the trigger to be external, not off the FPGA clock.
221#ifndef WPILIB2015
Austin Schuh91c75562015-12-20 22:23:10 -0800222 tdma_config_->writeExternalTriggers(channel_index / 4, channel_index % 4,
223 new_trigger, &status);
224 if (status != 0) {
225 wpi_setErrorWithContext(status, getHALErrorMessage(status));
226 return;
227 }
Austin Schuhb19fddb2015-11-22 22:25:29 -0800228#else
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800229 uint32_t current_triggers;
Austin Schuhb19fddb2015-11-22 22:25:29 -0800230 tRioStatusCode register_status =
231 NiFpga_ReadU32(0x10000, 0x1832c, &current_triggers);
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800232 if (register_status != 0) {
233 wpi_setErrorWithContext(register_status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500234 return;
235 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800236 current_triggers = (current_triggers & ~(0xff << (channel_index * 8))) |
237 (new_trigger.value << (channel_index * 8));
238 register_status = NiFpga_WriteU32(0x10000, 0x1832c, current_triggers);
239 if (register_status != 0) {
240 wpi_setErrorWithContext(register_status, getHALErrorMessage(status));
Brian Silverman2aa83d72015-01-24 18:03:11 -0500241 return;
242 }
Austin Schuhb19fddb2015-11-22 22:25:29 -0800243#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -0500244}
245
246DMA::ReadStatus DMA::Read(DMASample *sample, uint32_t timeout_ms,
Brian Silvermand49fd782015-01-30 16:43:17 -0500247 size_t *remaining_out) {
Brian Silverman2aa83d72015-01-24 18:03:11 -0500248 tRioStatusCode status = 0;
249 size_t remainingBytes = 0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500250 *remaining_out = 0;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500251
252 if (!manager_.get()) {
253 wpi_setErrorWithContext(NiFpga_Status_InvalidParameter,
254 "DMA::Read() only works after DMA::Start()");
255 return STATUS_ERROR;
256 }
257
Brian Silvermand49fd782015-01-30 16:43:17 -0500258 sample->dma_ = this;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500259 manager_->read(sample->read_buffer_, capture_size_, timeout_ms,
260 &remainingBytes, &status);
261
Brian Silverman2aa83d72015-01-24 18:03:11 -0500262 // TODO(jerry): Do this only if status == 0?
Brian Silvermand49fd782015-01-30 16:43:17 -0500263 *remaining_out = remainingBytes / capture_size_;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500264
Brian Silverman2aa83d72015-01-24 18:03:11 -0500265 // TODO(austin): Check that *remainingBytes % capture_size_ == 0 and deal
266 // with it if it isn't. Probably meant that we overflowed?
267 if (status == 0) {
268 return STATUS_OK;
269 } else if (status == NiFpga_Status_FifoTimeout) {
270 return STATUS_TIMEOUT;
271 } else {
272 wpi_setErrorWithContext(status, getHALErrorMessage(status));
273 return STATUS_ERROR;
274 }
275}
276
Brian Silvermand49fd782015-01-30 16:43:17 -0500277const char *DMA::NameOfReadStatus(ReadStatus s) {
278 switch (s) {
279 case STATUS_OK: return "OK";
280 case STATUS_TIMEOUT: return "TIMEOUT";
281 case STATUS_ERROR: return "ERROR";
282 default: return "(bad ReadStatus code)";
283 }
284}
285
Brian Silverman2aa83d72015-01-24 18:03:11 -0500286void DMA::Start(size_t queue_depth) {
287 tRioStatusCode status = 0;
288 tconfig_ = tdma_config_->readConfig(&status);
289 wpi_setErrorWithContext(status, getHALErrorMessage(status));
290 if (status != 0) {
291 return;
292 }
293
294 {
295 size_t accum_size = 0;
296#define SET_SIZE(bit) \
297 if (tconfig_.bit) { \
298 channel_offsets_[k##bit] = accum_size; \
299 accum_size += kChannelSize[k##bit]; \
300 } else { \
301 channel_offsets_[k##bit] = -1; \
302 }
303
304 SET_SIZE(Enable_AI0_Low);
305 SET_SIZE(Enable_AI0_High);
306 SET_SIZE(Enable_AIAveraged0_Low);
307 SET_SIZE(Enable_AIAveraged0_High);
308 SET_SIZE(Enable_AI1_Low);
309 SET_SIZE(Enable_AI1_High);
310 SET_SIZE(Enable_AIAveraged1_Low);
311 SET_SIZE(Enable_AIAveraged1_High);
312 SET_SIZE(Enable_Accumulator0);
313 SET_SIZE(Enable_Accumulator1);
314 SET_SIZE(Enable_DI);
315 SET_SIZE(Enable_AnalogTriggers);
316 SET_SIZE(Enable_Counters_Low);
317 SET_SIZE(Enable_Counters_High);
318 SET_SIZE(Enable_CounterTimers_Low);
319 SET_SIZE(Enable_CounterTimers_High);
Austin Schuh91c75562015-12-20 22:23:10 -0800320#ifdef WPILIB2015
Brian Silverman2aa83d72015-01-24 18:03:11 -0500321 SET_SIZE(Enable_Encoders);
322 SET_SIZE(Enable_EncoderTimers);
Austin Schuh91c75562015-12-20 22:23:10 -0800323#else
324 SET_SIZE(Enable_Encoders_Low);
325 SET_SIZE(Enable_Encoders_High);
326 SET_SIZE(Enable_EncoderTimers_Low);
327 SET_SIZE(Enable_EncoderTimers_High);
328#endif
Brian Silverman2aa83d72015-01-24 18:03:11 -0500329#undef SET_SIZE
330 capture_size_ = accum_size + 1;
331 }
332
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800333 manager_.reset(
334 new nFPGA::tDMAManager(0, queue_depth * capture_size_, &status));
335
Brian Silverman2aa83d72015-01-24 18:03:11 -0500336 wpi_setErrorWithContext(status, getHALErrorMessage(status));
337 if (status != 0) {
338 return;
339 }
340 // Start, stop, start to clear the buffer.
341 manager_->start(&status);
342 wpi_setErrorWithContext(status, getHALErrorMessage(status));
343 if (status != 0) {
344 return;
345 }
346 manager_->stop(&status);
347 wpi_setErrorWithContext(status, getHALErrorMessage(status));
348 if (status != 0) {
349 return;
350 }
351 manager_->start(&status);
352 wpi_setErrorWithContext(status, getHALErrorMessage(status));
353 if (status != 0) {
354 return;
355 }
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800356
Brian Silverman2aa83d72015-01-24 18:03:11 -0500357}
358
Brian Silvermand49fd782015-01-30 16:43:17 -0500359static_assert(::std::is_pod<DMASample>::value, "DMASample needs to be POD");
360
Brian Silverman2aa83d72015-01-24 18:03:11 -0500361ssize_t DMASample::offset(int index) const { return dma_->channel_offsets_[index]; }
362
Austin Schuh8f314a92015-11-22 21:35:40 -0800363uint32_t DMASample::GetTime() const {
364 return read_buffer_[dma_->capture_size_ - 1];
365}
366
Brian Silverman2aa83d72015-01-24 18:03:11 -0500367double DMASample::GetTimestamp() const {
Austin Schuh8f314a92015-11-22 21:35:40 -0800368 return static_cast<double>(GetTime()) * 0.000001;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500369}
370
371bool DMASample::Get(DigitalSource *input) const {
372 if (offset(kEnable_DI) == -1) {
Austin Schuh91c75562015-12-20 22:23:10 -0800373 wpi_setStaticErrorWithContext(
374 dma_, NiFpga_Status_ResourceNotFound,
Brian Silverman2aa83d72015-01-24 18:03:11 -0500375 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
376 return false;
377 }
378 if (input->GetChannelForRouting() < kNumHeaders) {
Austin Schuh91c75562015-12-20 22:23:10 -0800379 return (read_buffer_[offset(kEnable_DI)] >> input->GetChannelForRouting()) &
Brian Silverman2aa83d72015-01-24 18:03:11 -0500380 0x1;
381 } else {
382 return (read_buffer_[offset(kEnable_DI)] >>
383 (input->GetChannelForRouting() + 6)) &
384 0x1;
385 }
386}
387
388int32_t DMASample::GetRaw(Encoder *input) const {
Austin Schuh91c75562015-12-20 22:23:10 -0800389 int index = input->GetFPGAIndex();
390 uint32_t dmaWord = 0;
391#ifdef WPILIB2015
392 if (index >= 4 || offset(kEnable_Encoders) == -1) {
393 wpi_setStaticErrorWithContext(
394 dma_, NiFpga_Status_ResourceNotFound,
Brian Silverman2aa83d72015-01-24 18:03:11 -0500395 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
396 return -1;
397 }
Austin Schuh91c75562015-12-20 22:23:10 -0800398 dmaWord = read_buffer_[offset(kEnable_Encoders) + index];
399#else
400 if (index < 4) {
401 if (offset(kEnable_Encoders_Low) == -1) {
402 wpi_setStaticErrorWithContext(
403 dma_, NiFpga_Status_ResourceNotFound,
404 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
405 return -1;
406 }
407 dmaWord = read_buffer_[offset(kEnable_Encoders_Low) + index];
408 } else if (index < 8) {
409 if (offset(kEnable_Encoders_High) == -1) {
410 wpi_setStaticErrorWithContext(
411 dma_, NiFpga_Status_ResourceNotFound,
412 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
413 return -1;
414 }
415 dmaWord = read_buffer_[offset(kEnable_Encoders_High) + (index - 4)];
416 } else {
417 wpi_setStaticErrorWithContext(
418 dma_, NiFpga_Status_ResourceNotFound,
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800419 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
Austin Schuh91c75562015-12-20 22:23:10 -0800420 return 0;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800421 }
Austin Schuh91c75562015-12-20 22:23:10 -0800422#endif
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800423
Brian Silverman2aa83d72015-01-24 18:03:11 -0500424 int32_t result = 0;
425
Austin Schuhb19fddb2015-11-22 22:25:29 -0800426 // Extract the 31-bit signed tEncoder::tOutput Value using a struct with the
427 // reverse packed field order of tOutput. This gets Value from the high
428 // order 31 bits of output on little-endian ARM using gcc. This works
429 // even though C/C++ doesn't guarantee bitfield order.
430 t1Output output;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500431
Austin Schuhb19fddb2015-11-22 22:25:29 -0800432 output.value = dmaWord;
433 result = output.Value;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500434
435 return result;
436}
437
438int32_t DMASample::Get(Encoder *input) const {
439 int32_t raw = GetRaw(input);
440
Brian Silvermand49fd782015-01-30 16:43:17 -0500441 return raw / input->GetEncodingScale();
442}
443
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800444uint16_t DMASample::GetValue(AnalogInput *input) const {
Austin Schuh91c75562015-12-20 22:23:10 -0800445 uint32_t channel = input->GetChannel();
446 uint32_t dmaWord;
447 if (channel < 4) {
448 if (offset(kEnable_AI0_Low) == -1) {
449 wpi_setStaticErrorWithContext(
450 dma_, NiFpga_Status_ResourceNotFound,
451 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
452 return 0xffff;
453 }
454 dmaWord = read_buffer_[offset(kEnable_AI0_Low) + channel / 2];
455 } else if (channel < 8) {
456 if (offset(kEnable_AI0_High) == -1) {
457 wpi_setStaticErrorWithContext(
458 dma_, NiFpga_Status_ResourceNotFound,
459 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
460 return 0xffff;
461 }
462 dmaWord = read_buffer_[offset(kEnable_AI0_High) + (channel - 4) / 2];
463 } else {
464 wpi_setStaticErrorWithContext(
465 dma_, NiFpga_Status_ResourceNotFound,
Brian Silvermand49fd782015-01-30 16:43:17 -0500466 getHALErrorMessage(NiFpga_Status_ResourceNotFound));
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800467 return 0xffff;
Brian Silvermand49fd782015-01-30 16:43:17 -0500468 }
Austin Schuhc6cc4102015-02-15 23:19:53 -0800469 if (channel % 2) {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800470 return (dmaWord >> 16) & 0xffff;
471 } else {
Austin Schuh91c75562015-12-20 22:23:10 -0800472 return dmaWord & 0xffff;
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800473 }
Brian Silvermand49fd782015-01-30 16:43:17 -0500474 return static_cast<int16_t>(dmaWord);
475}
476
477float DMASample::GetVoltage(AnalogInput *input) const {
Austin Schuh58d8cdf2015-02-15 21:04:42 -0800478 uint16_t value = GetValue(input);
479 if (value == 0xffff) return 0.0;
Brian Silvermand49fd782015-01-30 16:43:17 -0500480 uint32_t lsb_weight = input->GetLSBWeight();
481 int32_t offset = input->GetOffset();
482 float voltage = lsb_weight * 1.0e-9 * value - offset * 1.0e-9;
483 return voltage;
Brian Silverman2aa83d72015-01-24 18:03:11 -0500484}