blob: 000cf86ebc0beea5ebd98e38b735fb3413228dc0 [file] [log] [blame]
jerrymf1579332013-02-07 01:56:28 +00001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2008. All Rights Reserved. */
3/* Open Source Software - may be modified and shared by FRC teams. The code */
4/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */
5/*----------------------------------------------------------------------------*/
6
7#include "DriverStationEnhancedIO.h"
8#include "NetworkCommunication/UsageReporting.h"
9#include "Synchronized.h"
10#include "WPIErrors.h"
11#include <strLib.h>
12
13/**
14 * DriverStationEnhancedIO contructor.
15 *
16 * This is only called once when the DriverStation constructor is called.
17 */
18DriverStationEnhancedIO::DriverStationEnhancedIO()
19 : m_inputValid (false)
20 , m_outputValid (false)
21 , m_configChanged (false)
22 , m_requestEnhancedEnable (false)
23{
24 bzero((char*)&m_inputData, sizeof(m_inputData));
25 bzero((char*)&m_outputData, sizeof(m_outputData));
26 m_outputData.size = sizeof(m_outputData) - 1;
27 m_outputData.id = kOutputBlockID;
28 // Expected to be active low, so initialize inactive.
29 m_outputData.data.fixed_digital_out = 0x3;
30 m_inputDataSemaphore = semMCreate(SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE);
31 m_outputDataSemaphore = semMCreate(SEM_Q_PRIORITY | SEM_DELETE_SAFE | SEM_INVERSION_SAFE);
32 m_encoderOffsets[0] = 0;
33 m_encoderOffsets[1] = 0;
34}
35
36/**
37 * DriverStationEnhancedIO destructor.
38 *
39 * Called only when the DriverStation class is destroyed.
40 */
41DriverStationEnhancedIO::~DriverStationEnhancedIO()
42{
43 semDelete(m_outputDataSemaphore);
44 semDelete(m_inputDataSemaphore);
45}
46
47/**
48 * Called by the DriverStation class when data is available.
49 * This function will set any modified configuration / output,
50 * then read the input and configuration from the IO.
51 */
52void DriverStationEnhancedIO::UpdateData()
53{
54 INT32 retVal;
55 {
56 status_block_t tempOutputData;
57 Synchronized sync(m_outputDataSemaphore);
58 if (m_outputValid || m_configChanged || m_requestEnhancedEnable)
59 {
60 m_outputData.flags = kStatusValid;
61 if (m_requestEnhancedEnable)
62 {
63 // Someone called one of the get config APIs, but we are not in enhanced mode.
64 m_outputData.flags |= kForceEnhancedMode;
65 }
66 if (m_configChanged)
67 {
68 if (!m_outputValid)
69 {
70 // Someone called one of the set config APIs, but we are not in enhanced mode.
71 m_outputData.flags |= kForceEnhancedMode;
72 }
73 m_outputData.flags |= kStatusConfigChanged;
74 }
75 overrideIOConfig((char*)&m_outputData, 5);
76 }
77 retVal = getDynamicControlData(kOutputBlockID, (char*)&tempOutputData, sizeof(status_block_t), 5);
78 if (retVal == 0)
79 {
80 if (m_outputValid)
81 {
82 if (m_configChanged)
83 {
84 // If our config change made the round trip then clear the flag.
85 if (IsConfigEqual(tempOutputData, m_outputData))
86 {
87 m_configChanged = false;
88 }
89 }
90 else
91 {
92 // TODO: This won't work until artf1128 is fixed
93 //if (tempOutputData.flags & kStatusConfigChanged)
94 {
95 // Configuration was updated on the DS, so update our local cache.
96 MergeConfigIntoOutput(tempOutputData, m_outputData);
97 }
98 }
99 }
100 else
101 {
102 // Initialize the local cache.
103 MergeConfigIntoOutput(tempOutputData, m_outputData);
104 }
105 m_requestEnhancedEnable = false;
106 m_outputValid = true;
107 }
108 else
109 {
110 m_outputValid = false;
111 m_inputValid = false;
112 }
113 }
114 {
115 Synchronized sync(m_inputDataSemaphore);
116 control_block_t tempInputData;
117 retVal = getDynamicControlData(kInputBlockID, (char*)&tempInputData, sizeof(control_block_t), 5);
118 if (retVal == 0 && tempInputData.data.api_version == kSupportedAPIVersion)
119 {
120 m_inputData = tempInputData;
121 m_inputValid = true;
122 }
123 else
124 {
125 m_outputValid = false;
126 m_inputValid = false;
127 }
128 }
129}
130
131/**
132 * Merge the config portion of the DS output block into the local cache.
133 */
134void DriverStationEnhancedIO::MergeConfigIntoOutput(const status_block_t &dsOutputBlock, status_block_t &localCache)
135{
136 localCache.data.digital = (localCache.data.digital & dsOutputBlock.data.digital_oe) |
137 (dsOutputBlock.data.digital & ~dsOutputBlock.data.digital_oe);
138 localCache.data.digital_oe = dsOutputBlock.data.digital_oe;
139 localCache.data.digital_pe = dsOutputBlock.data.digital_pe;
140 localCache.data.pwm_period[0] = dsOutputBlock.data.pwm_period[0];
141 localCache.data.pwm_period[1] = dsOutputBlock.data.pwm_period[1];
142 localCache.data.enables = dsOutputBlock.data.enables;
143}
144
145/**
146 * Compare the config portion of the output blocks.
147 */
148bool DriverStationEnhancedIO::IsConfigEqual(const status_block_t &dsOutputBlock, const status_block_t &localCache)
149{
150 if (localCache.data.digital_oe != dsOutputBlock.data.digital_oe) return false;
151 if ((localCache.data.digital & ~dsOutputBlock.data.digital) !=
152 (dsOutputBlock.data.digital & ~dsOutputBlock.data.digital)) return false;
153 if (localCache.data.digital_pe != dsOutputBlock.data.digital_pe) return false;
154 if (localCache.data.pwm_period[0] != dsOutputBlock.data.pwm_period[0]) return false;
155 if (localCache.data.pwm_period[1] != dsOutputBlock.data.pwm_period[1]) return false;
156 if (localCache.data.enables != dsOutputBlock.data.enables) return false;
157 return true;
158}
159
160/**
161 * Query an accelerometer channel on the DS IO.
162 *
163 * @param channel The channel number to read.
164 * @return The current acceleration on the channel in Gs.
165 */
166double DriverStationEnhancedIO::GetAcceleration(tAccelChannel channel)
167{
168 if (channel < 1 || channel > 2)
169 {
170 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 2");
171 return 0.0;
172 }
173 if (!m_inputValid)
174 {
175 wpi_setWPIError(EnhancedIOMissing);
176 return 0.0;
177 }
178
179 static UINT8 reported_mask = 0;
180 if (!(reported_mask & (1 >> channel)))
181 {
182 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_Acceleration);
183 reported_mask |= (1 >> channel);
184 }
185
186 Synchronized sync(m_inputDataSemaphore);
187 return (m_inputData.data.accel[channel] - kAccelOffset) / kAccelScale;
188}
189
190/**
191 * Query an analog input channel on the DS IO.
192 *
193 * @param channel The channel number to read. [1,8]
194 * @return The analog input voltage for the channel.
195 */
196double DriverStationEnhancedIO::GetAnalogIn(UINT32 channel)
197{
198 // 3.3V is the analog reference voltage
199 return GetAnalogInRatio(channel) * kAnalogInputReference;
200}
201
202/**
203 * Query an analog input channel on the DS IO in ratiometric form.
204 *
205 * @param channel The channel number to read. [1,8]
206 * @return The analog input percentage for the channel.
207 */
208double DriverStationEnhancedIO::GetAnalogInRatio(UINT32 channel)
209{
210 if (channel < 1 || channel > 8)
211 {
212 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 8");
213 return 0.0;
214 }
215 if (!m_inputValid)
216 {
217 wpi_setWPIError(EnhancedIOMissing);
218 return 0.0;
219 }
220
221 static UINT16 reported_mask = 0;
222 if (!(reported_mask & (1 >> channel)))
223 {
224 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_AnalogIn);
225 reported_mask |= (1 >> channel);
226 }
227
228 Synchronized sync(m_inputDataSemaphore);
229 return m_inputData.data.analog[channel-1] / kAnalogInputResolution;
230}
231
232/**
233 * Query the voltage currently being output.
234 *
235 * AO1 is pin 11 on the top connector (P2).
236 * AO2 is pin 12 on the top connector (P2).
237 *
238 * @param channel The analog output channel on the DS IO. [1,2]
239 * @return The voltage being output on the channel.
240 */
241double DriverStationEnhancedIO::GetAnalogOut(UINT32 channel)
242{
243 if (channel < 1 || channel > 2)
244 {
245 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 2");
246 return 0.0;
247 }
248 if (!m_outputValid)
249 {
250 wpi_setWPIError(EnhancedIOMissing);
251 return 0.0;
252 }
253
254 Synchronized sync(m_outputDataSemaphore);
255 return m_outputData.data.dac[channel-1] * kAnalogOutputReference / kAnalogOutputResolution;
256}
257
258/**
259 * Set the analog output voltage.
260 *
261 * AO1 is pin 11 on the top connector (P2).
262 * AO2 is pin 12 on the top connector (P2).
263 * AO1 is the reference voltage for the 2 analog comparators on DIO15 and DIO16.
264 *
265 * The output range is 0V to 4V, however due to the supply voltage don't expect more than about 3V.
266 * Current supply capability is only 100uA.
267 *
268 * @param channel The analog output channel on the DS IO. [1,2]
269 * @param value The voltage to output on the channel.
270 */
271void DriverStationEnhancedIO::SetAnalogOut(UINT32 channel, double value)
272{
273 if (channel < 1 || channel > 2)
274 {
275 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 2");
276 return;
277 }
278 if (!m_outputValid)
279 {
280 wpi_setWPIError(EnhancedIOMissing);
281 return;
282 }
283 if (value < 0.0) value = 0.0;
284 if (value > kAnalogOutputReference) value = kAnalogOutputReference;
285
286 static UINT8 reported_mask = 0;
287 if (!(reported_mask & (1 >> channel)))
288 {
289 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_AnalogOut);
290 reported_mask |= (1 >> channel);
291 }
292
293 Synchronized sync(m_outputDataSemaphore);
294 m_outputData.data.dac[channel-1] = (UINT8)(value / kAnalogOutputReference * kAnalogOutputResolution);
295}
296
297/**
298 * Get the state of a button on the IO board.
299 *
300 * Button1 is the physical button "S1".
301 * Button2 is pin 4 on the top connector (P2).
302 * Button3 is pin 6 on the top connector (P2).
303 * Button4 is pin 8 on the top connector (P2).
304 * Button5 is pin 10 on the top connector (P2).
305 * Button6 is pin 7 on the top connector (P2).
306 *
307 * Button2 through Button6 are Capacitive Sense buttons.
308 *
309 * @param channel The button channel to read. [1,6]
310 * @return The state of the selected button.
311 */
312bool DriverStationEnhancedIO::GetButton(UINT32 channel)
313{
314 if (channel < 1 || channel > 6)
315 {
316 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 6");
317 return false;
318 }
319
320 static UINT8 reported_mask = 0;
321 if (!(reported_mask & (1 >> channel)))
322 {
323 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_Button);
324 reported_mask |= (1 >> channel);
325 }
326
327 return ((GetButtons() >> (channel-1)) & 1) != 0;
328}
329
330/**
331 * Get the state of all the button channels.
332 *
333 * @return The state of the 6 button channels in the 6 lsb of the returned byte.
334 */
335UINT8 DriverStationEnhancedIO::GetButtons()
336{
337 if (!m_inputValid)
338 {
339 wpi_setWPIError(EnhancedIOMissing);
340 return 0;
341 }
342 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, 0, nUsageReporting::kDriverStationEIO_Button);
343 Synchronized sync(m_inputDataSemaphore);
344 return m_inputData.data.buttons;
345}
346
347/**
348 * Set the state of an LED on the IO board.
349 *
350 * @param channel The LED channel to set. [1,8]
351 * @param value True to turn the LED on.
352 */
353void DriverStationEnhancedIO::SetLED(UINT32 channel, bool value)
354{
355 if (channel < 1 || channel > 8)
356 {
357 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 8");
358 return;
359 }
360 if (!m_outputValid)
361 {
362 wpi_setWPIError(EnhancedIOMissing);
363 return;
364 }
365
366 static UINT16 reported_mask = 0;
367 if (!(reported_mask & (1 >> channel)))
368 {
369 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_LED);
370 reported_mask |= (1 >> channel);
371 }
372
373 UINT8 leds;
374 Synchronized sync(m_outputDataSemaphore);
375 leds = m_outputData.data.leds;
376
377 leds &= ~(1 << (channel-1));
378 if (value) leds |= 1 << (channel-1);
379
380 m_outputData.data.leds = leds;
381}
382
383/**
384 * Set the state of all 8 LEDs on the IO board.
385 *
386 * @param value The state of each LED. LED1 is lsb and LED8 is msb.
387 */
388void DriverStationEnhancedIO::SetLEDs(UINT8 value)
389{
390 if (!m_outputValid)
391 {
392 wpi_setWPIError(EnhancedIOMissing);
393 return;
394 }
395 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, 0, nUsageReporting::kDriverStationEIO_LED);
396 Synchronized sync(m_outputDataSemaphore);
397 m_outputData.data.leds = value;
398}
399
400/**
401 * Get the current state of a DIO channel regardless of mode.
402 *
403 * @param channel The DIO channel to read. [1,16]
404 * @return The state of the selected digital line.
405 */
406bool DriverStationEnhancedIO::GetDigital(UINT32 channel)
407{
408 if (channel < 1 || channel > 16)
409 {
410 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 16");
411 return false;
412 }
413
414 static UINT32 reported_mask = 0;
415 if (!(reported_mask & (1 >> channel)))
416 {
417 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_DigitalIn);
418 reported_mask |= (1 >> channel);
419 }
420
421 return ((GetDigitals() >> (channel-1)) & 1) != 0;
422}
423
424/**
425 * Get the state of all 16 DIO lines regardless of mode.
426 *
427 * @return The state of all DIO lines. DIO1 is lsb and DIO16 is msb.
428 */
429UINT16 DriverStationEnhancedIO::GetDigitals()
430{
431 if (!m_inputValid)
432 {
433 wpi_setWPIError(EnhancedIOMissing);
434 return 0;
435 }
436 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, 0, nUsageReporting::kDriverStationEIO_DigitalIn);
437 Synchronized sync(m_inputDataSemaphore);
438 return m_inputData.data.digital;
439}
440
441/**
442 * Set the state of a DIO line that is configured for digital output.
443 *
444 * @param channel The DIO channel to set. [1,16]
445 * @param value The state to set the selected channel to.
446 */
447void DriverStationEnhancedIO::SetDigitalOutput(UINT32 channel, bool value)
448{
449 if (channel < 1 || channel > 16)
450 {
451 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 16");
452 return;
453 }
454 if (!m_outputValid)
455 {
456 wpi_setWPIError(EnhancedIOMissing);
457 return;
458 }
459
460 static UINT32 reported_mask = 0;
461 if (!(reported_mask & (1 >> channel)))
462 {
463 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_DigitalOut);
464 reported_mask |= (1 >> channel);
465 }
466
467 UINT16 digital;
468 Synchronized sync(m_outputDataSemaphore);
469
470 if (m_outputData.data.digital_oe & (1 << (channel-1)))
471 {
472 digital = m_outputData.data.digital;
473
474 digital &= ~(1 << (channel-1));
475 if (value) digital |= 1 << (channel-1);
476
477 m_outputData.data.digital = digital;
478 }
479 else
480 {
481 wpi_setWPIError(LineNotOutput);
482 }
483}
484
485/**
486 * Get the current configuration for a DIO line.
487 *
488 * This has the side effect of forcing the Driver Station to switch to Enhanced mode if it's not when called.
489 * If Enhanced mode is not enabled when this is called, it will return kUnknown.
490 *
491 * @param channel The DIO channel config to get. [1,16]
492 * @return The configured mode for the DIO line.
493 */
494DriverStationEnhancedIO::tDigitalConfig DriverStationEnhancedIO::GetDigitalConfig(UINT32 channel)
495{
496 if (channel < 1 || channel > 16)
497 {
498 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 16");
499 return kUnknown;
500 }
501 if (!m_outputValid)
502 {
503 m_requestEnhancedEnable = true;
504 wpi_setWPIError(EnhancedIOMissing);
505 return kUnknown;
506 }
507 Synchronized sync(m_outputDataSemaphore);
508 if ((channel >= 1) && (channel <= 4))
509 {
510 if (m_outputData.data.pwm_enable & (1 << (channel - 1)))
511 {
512 return kPWM;
513 }
514 }
515 if ((channel >= 15) && (channel <= 16))
516 {
517 if (m_outputData.data.comparator_enable & (1 << (channel - 15)))
518 {
519 return kAnalogComparator;
520 }
521 }
522 if (m_outputData.data.digital_oe & (1 << (channel - 1)))
523 {
524 return kOutput;
525 }
526 if (!(m_outputData.data.digital_pe & (1 << (channel - 1))))
527 {
528 return kInputFloating;
529 }
530 if (m_outputData.data.digital & (1 << (channel - 1)))
531 {
532 return kInputPullUp;
533 }
534 else
535 {
536 return kInputPullDown;
537 }
538}
539
540/**
541 * Override the DS's configuration of a DIO line.
542 *
543 * If configured to kInputFloating, the selected DIO line will be tri-stated with no internal pull resistor.
544 *
545 * If configured to kInputPullUp, the selected DIO line will be tri-stated with a 5k-Ohm internal pull-up resistor enabled.
546 *
547 * If configured to kInputPullDown, the selected DIO line will be tri-stated with a 5k-Ohm internal pull-down resistor enabled.
548 *
549 * If configured to kOutput, the selected DIO line will actively drive to 0V or Vddio (specified by J1 and J4).
550 * DIO1 through DIO12, DIO15, and DIO16 can source 4mA and can sink 8mA.
551 * DIO12 and DIO13 can source 4mA and can sink 25mA.
552 *
553 * In addition to the common configurations, DIO1 through DIO4 can be configured to kPWM to enable PWM output.
554 *
555 * In addition to the common configurations, DIO15 and DIO16 can be configured to kAnalogComparator to enable
556 * analog comparators on those 2 DIO lines. When enabled, the lines are tri-stated and will accept analog voltages
557 * between 0V and 3.3V. If the input voltage is greater than the voltage output by AO1, the DIO will read as true,
558 * if less then false.
559 *
560 * @param channel The DIO line to configure. [1,16]
561 * @param config The mode to put the DIO line in.
562 */
563void DriverStationEnhancedIO::SetDigitalConfig(UINT32 channel, tDigitalConfig config)
564{
565 if (channel < 1 || channel > 16)
566 {
567 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 16");
568 return;
569 }
570 if (config == kPWM && (channel < 1 || channel > 4))
571 {
572 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel in PWM mode must be between 1 and 4");
573 return;
574 }
575 if (config == kAnalogComparator && (channel < 15 || channel > 16))
576 {
577 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel in Analog Comparator mode must be between 15 and 16");
578 return;
579 }
580
581 Synchronized sync(m_outputDataSemaphore);
582 m_configChanged = true;
583
584 if ((channel >= 1) && (channel <= 4))
585 {
586 if (config == kPWM)
587 {
588 m_outputData.data.pwm_enable |= 1 << (channel - 1);
589 m_outputData.data.digital &= ~(1 << (channel - 1));
590 m_outputData.data.digital_oe |= 1 << (channel - 1);
591 m_outputData.data.digital_pe &= ~(1 << (channel - 1));
592 return;
593 }
594 else
595 {
596 m_outputData.data.pwm_enable &= ~(1 << (channel - 1));
597 }
598 }
599 else if ((channel >= 15) && (channel <= 16))
600 {
601 if (config == kAnalogComparator)
602 {
603 m_outputData.data.comparator_enable |= 1 << (channel - 15);
604 m_outputData.data.digital &= ~(1 << (channel - 1));
605 m_outputData.data.digital_oe &= ~(1 << (channel - 1));
606 m_outputData.data.digital_pe &= ~(1 << (channel - 1));
607 return;
608 }
609 else
610 {
611 m_outputData.data.comparator_enable &= ~(1 << (channel - 15));
612 }
613 }
614 if (config == kInputFloating)
615 {
616 m_outputData.data.digital &= ~(1 << (channel - 1));
617 m_outputData.data.digital_oe &= ~(1 << (channel - 1));
618 m_outputData.data.digital_pe &= ~(1 << (channel - 1));
619 }
620 else if (config == kInputPullUp)
621 {
622 m_outputData.data.digital |= 1 << (channel - 1);
623 m_outputData.data.digital_oe &= ~(1 << (channel - 1));
624 m_outputData.data.digital_pe |= 1 << (channel - 1);
625 }
626 else if (config == kInputPullDown)
627 {
628 m_outputData.data.digital &= ~(1 << (channel - 1));
629 m_outputData.data.digital_oe &= ~(1 << (channel - 1));
630 m_outputData.data.digital_pe |= 1 << (channel - 1);
631 }
632 else if (config == kOutput)
633 {
634 m_outputData.data.digital_oe |= 1 << (channel - 1);
635 m_outputData.data.digital_pe &= ~(1 << (channel - 1));
636 }
637 else
638 {
639 // Something went wrong.
640 }
641}
642
643/**
644 * Get the period of a PWM generator.
645 *
646 * This has the side effect of forcing the Driver Station to switch to Enhanced mode if it's not when called.
647 * If Enhanced mode is not enabled when this is called, it will return 0.
648 *
649 * @param channels Select the generator by specifying the two channels to which it is connected.
650 * @return The period of the PWM generator in seconds.
651 */
652double DriverStationEnhancedIO::GetPWMPeriod(tPWMPeriodChannels channels)
653{
654 if (channels < kPWMChannels1and2 || channels > kPWMChannels3and4)
655 {
656 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channels must be kPWMChannels1and2 or kPWMChannels3and4");
657 return 0.0;
658 }
659 if (!m_outputValid)
660 {
661 m_requestEnhancedEnable = true;
662 wpi_setWPIError(EnhancedIOMissing);
663 return 0.0;
664 }
665
666 Synchronized sync(m_outputDataSemaphore);
667 return m_outputData.data.pwm_period[channels] / 24000000.0;
668}
669
670/**
671 * Set the period of a PWM generator.
672 *
673 * There are 2 PWM generators on the IO board. One can generate PWM signals on DIO1 and DIO2,
674 * the other on DIO3 and DIO4. Each generator has one counter and two compare registers. As such,
675 * each pair of PWM outputs share the output period but have independent duty cycles.
676 *
677 * @param channels Select the generator by specifying the two channels to which it is connected.
678 * @param period The period of the PWM generator in seconds. [0.0,0.002731]
679 */
680void DriverStationEnhancedIO::SetPWMPeriod(tPWMPeriodChannels channels, double period)
681{
682 if (channels < kPWMChannels1and2 || channels > kPWMChannels3and4)
683 {
684 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channels must be kPWMChannels1and2 or kPWMChannels3and4");
685 return;
686 }
687
688 // Convert to ticks based on the IO board's 24MHz clock
689 double ticks = period * 24000000.0;
690 // Limit the range of the ticks... warn if too big.
691 if (ticks > 65534.0)
692 {
693 wpi_setWPIError(EnhancedIOPWMPeriodOutOfRange);
694 ticks = 65534.0;
695 }
696 else if (ticks < 0.0) ticks = 0.0;
697 // Preserve the duty cycles.
698 double dutyCycles[2];
699 dutyCycles[0] = GetPWMOutput((channels << 1) + 1);
700 dutyCycles[1] = GetPWMOutput((channels << 1) + 2);
701 {
702 Synchronized sync(m_outputDataSemaphore);
703 // Update the period
704 m_outputData.data.pwm_period[channels] = (UINT16)ticks;
705 m_configChanged = true;
706 }
707 // Restore the duty cycles
708 SetPWMOutput((channels << 1) + 1, dutyCycles[0]);
709 SetPWMOutput((channels << 1) + 2, dutyCycles[1]);
710}
711
712/**
713 * Get the state being output on a fixed digital output.
714 *
715 * @param channel The FixedDO line to get. [1,2]
716 * @return The state of the FixedDO line.
717 */
718bool DriverStationEnhancedIO::GetFixedDigitalOutput(UINT32 channel)
719{
720 if (channel < 1 || channel > 2)
721 {
722 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 2");
723 return 0;
724 }
725 if (!m_outputValid)
726 {
727 wpi_setWPIError(EnhancedIOMissing);
728 return 0;
729 }
730
731 Synchronized sync(m_outputDataSemaphore);
732 return ((m_outputData.data.fixed_digital_out >> (channel-1)) & 1) != 0;
733}
734
735/**
736 * Set the state to output on a Fixed High Current Digital Output line.
737 *
738 * FixedDO1 is pin 5 on the top connector (P2).
739 * FixedDO2 is pin 3 on the top connector (P2).
740 *
741 * The FixedDO lines always output 0V and 3.3V regardless of J1 and J4.
742 * They can source 4mA and can sink 25mA. Because of this, they are expected to be used
743 * in an active low configuration, such as connecting to the cathode of a bright LED.
744 * Because they are expected to be active low, they default to true.
745 *
746 * @param channel The FixedDO channel to set.
747 * @param value The state to set the FixedDO.
748 */
749void DriverStationEnhancedIO::SetFixedDigitalOutput(UINT32 channel, bool value)
750{
751 if (channel < 1 || channel > 2)
752 {
753 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 2");
754 return;
755 }
756 if (!m_outputValid)
757 {
758 wpi_setWPIError(EnhancedIOMissing);
759 return;
760 }
761
762 static UINT8 reported_mask = 0;
763 if (!(reported_mask & (1 >> channel)))
764 {
765 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_FixedDigitalOut);
766 reported_mask |= (1 >> channel);
767 }
768
769 UINT8 digital;
770 Synchronized sync(m_outputDataSemaphore);
771 digital = m_outputData.data.fixed_digital_out;
772
773 digital &= ~(1 << (channel-1));
774 if (value) digital |= 1 << (channel-1);
775
776 m_outputData.data.fixed_digital_out = digital;
777}
778
779/**
780 * Get the position of a quadrature encoder.
781 *
782 * There are two signed 16-bit 4X quadrature decoders on the IO board. These decoders are always monitoring
783 * the state of the lines assigned to them, but these lines do not have to be used for encoders.
784 *
785 * Encoder1 uses DIO4 for "A", DIO6 for "B", and DIO8 for "Index".
786 * Encoder2 uses DIO5 for "A", DIO7 for "B", and DIO9 for "Index".
787 *
788 * The index functionality can be enabled or disabled using SetEncoderIndexEnable().
789 *
790 * @param encoderNumber The quadrature encoder to access. [1,2]
791 * @return The current position of the quadrature encoder.
792 */
793INT16 DriverStationEnhancedIO::GetEncoder(UINT32 encoderNumber)
794{
795 if (encoderNumber < 1 || encoderNumber > 2)
796 {
797 wpi_setWPIErrorWithContext(ParameterOutOfRange, "encoderNumber must be between 1 and 2");
798 return 0;
799 }
800 if (!m_inputValid)
801 {
802 wpi_setWPIError(EnhancedIOMissing);
803 return 0;
804 }
805
806 static UINT8 reported_mask = 0;
807 if (!(reported_mask & (1 >> encoderNumber)))
808 {
809 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, encoderNumber, nUsageReporting::kDriverStationEIO_Encoder);
810 reported_mask |= (1 >> encoderNumber);
811 }
812
813 Synchronized sync(m_inputDataSemaphore);
814 return m_inputData.data.quad[encoderNumber - 1] - m_encoderOffsets[encoderNumber - 1];
815}
816
817/**
818 * Reset the position of an encoder to 0.
819 *
820 * This simply stores an offset locally. It does not reset the hardware counter on the IO board.
821 * If you use this method with Index enabled, you may get unexpected results.
822 *
823 * @param encoderNumber The quadrature encoder to reset. [1,2]
824 */
825void DriverStationEnhancedIO::ResetEncoder(UINT32 encoderNumber)
826{
827 if (encoderNumber < 1 || encoderNumber > 2)
828 {
829 wpi_setWPIErrorWithContext(ParameterOutOfRange, "encoderNumber must be between 1 and 2");
830 return;
831 }
832 if (!m_inputValid)
833 {
834 wpi_setWPIError(EnhancedIOMissing);
835 return;
836 }
837
838 Synchronized sync(m_inputDataSemaphore);
839 m_encoderOffsets[encoderNumber - 1] = m_inputData.data.quad[encoderNumber - 1];
840}
841
842/**
843 * Get the current configuration of a quadrature encoder index channel.
844 *
845 * This has the side effect of forcing the Driver Station to switch to Enhanced mode if it's not when called.
846 * If Enhanced mode is not enabled when this is called, it will return false.
847 *
848 * @param encoderNumber The quadrature encoder. [1,2]
849 * @return Is the index channel of the encoder enabled.
850 */
851bool DriverStationEnhancedIO::GetEncoderIndexEnable(UINT32 encoderNumber)
852{
853 if (encoderNumber < 1 || encoderNumber > 2)
854 {
855 wpi_setWPIErrorWithContext(ParameterOutOfRange, "encoderNumber must be between 1 and 2");
856 return false;
857 }
858 if (!m_outputValid)
859 {
860 m_requestEnhancedEnable = true;
861 wpi_setWPIError(EnhancedIOMissing);
862 return false;
863 }
864
865 Synchronized sync(m_outputDataSemaphore);
866 return ((m_outputData.data.quad_index_enable >> (encoderNumber - 1)) & 1) != 0;
867}
868
869/**
870 * Enable or disable the index channel of a quadrature encoder.
871 *
872 * The quadrature decoders on the IO board support an active-low index input.
873 *
874 * Encoder1 uses DIO8 for "Index".
875 * Encoder2 uses DIO9 for "Index".
876 *
877 * When enabled, the decoder's counter will be reset to 0 when A, B, and Index are all low.
878 *
879 * @param encoderNumber The quadrature encoder. [1,2]
880 * @param enable If true, reset the encoder in an index condition.
881 */
882void DriverStationEnhancedIO::SetEncoderIndexEnable(UINT32 encoderNumber, bool enable)
883{
884 if (encoderNumber < 1 || encoderNumber > 2)
885 {
886 wpi_setWPIErrorWithContext(ParameterOutOfRange, "encoderNumber must be between 1 and 2");
887 return;
888 }
889
890 Synchronized sync(m_outputDataSemaphore);
891 m_outputData.data.quad_index_enable &= ~(1 << (encoderNumber - 1));
892 if (enable) m_outputData.data.quad_index_enable |= 1 << (encoderNumber - 1);
893 m_configChanged = true;
894}
895
896/**
897 * Get the value of the Capacitive Sense touch slider.
898 *
899 * @return Value between 0.0 (toward center of board) and 1.0 (toward edge of board). -1.0 means no touch detected.
900 */
901double DriverStationEnhancedIO::GetTouchSlider()
902{
903 if (!m_inputValid)
904 {
905 wpi_setWPIError(EnhancedIOMissing);
906 return 0.0;
907 }
908
909 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, 1, nUsageReporting::kDriverStationEIO_TouchSlider);
910
911 Synchronized sync(m_inputDataSemaphore);
912 UINT8 value = m_inputData.data.capsense_slider;
913 return value == 255 ? -1.0 : value / 254.0;
914}
915
916/**
917 * Get the percent duty-cycle that the PWM generator channel is configured to output.
918 *
919 * @param channel The DIO line's PWM generator to get the duty-cycle from. [1,4]
920 * @return The percent duty-cycle being output (if the DIO line is configured for PWM). [0.0,1.0]
921 */
922double DriverStationEnhancedIO::GetPWMOutput(UINT32 channel)
923{
924 if (channel < 1 || channel > 4)
925 {
926 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 4");
927 return 0.0;
928 }
929 if (!m_outputValid)
930 {
931 wpi_setWPIError(EnhancedIOMissing);
932 return 0.0;
933 }
934
935 Synchronized sync(m_outputDataSemaphore);
936 return (double)m_outputData.data.pwm_compare[channel - 1] / (double)m_outputData.data.pwm_period[(channel - 1) >> 1];
937}
938
939/**
940 * Set the percent duty-cycle to output on a PWM enabled DIO line.
941 *
942 * DIO1 through DIO4 have the ability to output a PWM signal. The period of the
943 * signal can be configured in pairs using SetPWMPeriod().
944 *
945 * @param channel The DIO line's PWM generator to set. [1,4]
946 * @param value The percent duty-cycle to output from the PWM generator. [0.0,1.0]
947 */
948void DriverStationEnhancedIO::SetPWMOutput(UINT32 channel, double value)
949{
950 if (channel < 1 || channel > 4)
951 {
952 wpi_setWPIErrorWithContext(ParameterOutOfRange, "channel must be between 1 and 4");
953 return;
954 }
955 if (!m_outputValid)
956 {
957 wpi_setWPIError(EnhancedIOMissing);
958 return;
959 }
960
961 static UINT8 reported_mask = 0;
962 if (!(reported_mask & (1 >> channel)))
963 {
964 nUsageReporting::report(nUsageReporting::kResourceType_DriverStationEIO, channel, nUsageReporting::kDriverStationEIO_PWM);
965 reported_mask |= (1 >> channel);
966 }
967
968 if (value > 1.0) value = 1.0;
969 else if (value < 0.0) value = 0.0;
970 Synchronized sync(m_outputDataSemaphore);
971 m_outputData.data.pwm_compare[channel - 1] = (UINT16)(value * (double)m_outputData.data.pwm_period[(channel - 1) >> 1]);
972}
973
974/**
975 * Get the firmware version running on the IO board.
976 *
977 * This also has the side effect of forcing the driver station to switch to Enhanced mode if it is not.
978 * If you plan to switch between Driver Stations with unknown IO configurations, you can call this
979 * until it returns a non-0 version to ensure that this API is accessible before proceeding.
980 *
981 * @return The version of the firmware running on the IO board. 0 if the board is not attached or not in Enhanced mode.
982 */
983UINT8 DriverStationEnhancedIO::GetFirmwareVersion()
984{
985 if (!m_inputValid)
986 {
987 m_requestEnhancedEnable = true;
988 wpi_setWPIError(EnhancedIOMissing);
989 return 0;
990 }
991
992 Synchronized sync(m_inputDataSemaphore);
993 return m_inputData.data.fw_version;
994}
995