001    /*----------------------------------------------------------------------------*/
002    /* Copyright (c) FIRST 2008-2012. All Rights Reserved.                        */
003    /* Open Source Software - may be modified and shared by FRC teams. The code   */
004    /* must be accompanied by the FIRST BSD license file in the root directory of */
005    /* the project.                                                               */
006    /*----------------------------------------------------------------------------*/
007    package edu.wpi.first.wpilibj;
008    
009    import com.sun.cldc.jna.Pointer;
010    import com.sun.cldc.jna.Structure;
011    import edu.wpi.first.wpilibj.communication.FRCControl;
012    import edu.wpi.first.wpilibj.parsing.IInputOutput;
013    import edu.wpi.first.wpilibj.util.BoundaryException;
014    
015    /**
016     *
017     * @author dtjones
018     */
019    public class DriverStationEnhancedIO implements IInputOutput{
020    
021        static class output_t extends Structure {
022    
023            short digital = 0;
024            short digital_oe = 0;
025            short digital_pe = 0;
026            short[] pwm_compare = new short[4];
027            short[] pwm_period = new short[2];
028            byte[] dac = new byte[2];
029            byte leds = 0;
030            private byte enables = 0;
031            byte pwm_enable = 0; // :4
032            byte comparator_enable = 0; // :2
033            byte quad_index_enable = 0; // :2
034    //              union
035    //              {
036    //                      struct
037    //                      {
038    //                              // Bits are inverted from cypress fw because of big-endian!
039    //                              UINT8 pwm_enable : 4;
040    //                              UINT8 comparator_enable : 2;
041    //                              UINT8 quad_index_enable : 2;
042    //                      };
043    //                      UINT8 enables;
044    //              };
045            byte fixed_digital_out = 0;
046            
047            final static int size = 23;
048    
049            output_t(Pointer backingMemory) {
050                useMemory(backingMemory);
051            }
052    
053            public void setEnables(byte enablesByte) {
054                enables = enablesByte;
055                pwm_enable = (byte) ((enablesByte & (byte) 0xF0) >> 4);
056                comparator_enable = (byte) ((enablesByte & (byte) 0x0C) >> 2);
057                quad_index_enable = (byte) ((enablesByte & (byte) 0x03));
058            }
059    
060            public byte getEnables() {
061                enables = (byte) (((pwm_enable << 4) & (byte) 0xF0) |
062                        ((comparator_enable << 2) & (byte) 0x0C) |
063                        ((quad_index_enable) & (byte) 0x03));
064                return enables;
065            }
066    
067            public void read() {
068                digital = backingNativeMemory.getShort(0);
069                digital_oe = backingNativeMemory.getShort(2);
070                digital_pe = backingNativeMemory.getShort(4);
071                backingNativeMemory.getShorts(6, pwm_compare, 0, pwm_compare.length);
072                backingNativeMemory.getShorts(14, pwm_period, 0, pwm_period.length);
073                backingNativeMemory.getBytes(18, dac, 0, dac.length);
074                leds = backingNativeMemory.getByte(20);
075                setEnables(backingNativeMemory.getByte(21));
076                fixed_digital_out = backingNativeMemory.getByte(22);
077            }
078    
079            public void write() {
080                backingNativeMemory.setShort(0, digital);
081                backingNativeMemory.setShort(2, digital_oe);
082                backingNativeMemory.setShort(4, digital_pe);
083                backingNativeMemory.setShorts(6, pwm_compare, 0, pwm_compare.length);
084                backingNativeMemory.setShorts(14, pwm_period, 0, pwm_period.length);
085                backingNativeMemory.setBytes(18, dac, 0, dac.length);
086                backingNativeMemory.setByte(20, leds);
087                backingNativeMemory.setByte(21, getEnables());
088                backingNativeMemory.setByte(22, fixed_digital_out);
089            }
090    
091            public int size() {
092                return size;
093            }
094        }  //data to IO (23 bytes)
095    
096        // Dynamic block definitions
097        // END: Definitions from the Cypress firmware
098        static class input_t extends Structure {
099    
100            byte api_version;
101            byte fw_version;
102            short[] analog = new short[8];
103            short digital;
104            short[] accel = new short[3];
105            short[] quad = new short[2];
106            byte buttons;
107            byte capsense_slider;
108            byte capsense_proximity;
109            
110            final static int size = 33;
111    
112            input_t(Pointer backingMemory) {
113                useMemory(backingMemory);
114            }
115    
116            public void read() {
117                api_version = backingNativeMemory.getByte(0);
118                fw_version = backingNativeMemory.getByte(1);
119                backingNativeMemory.getShorts(2, analog, 0, analog.length);
120                digital = backingNativeMemory.getShort(18);
121                backingNativeMemory.getShorts(20, accel, 0, accel.length);
122                backingNativeMemory.getShorts(26, quad, 0, quad.length);
123                buttons = backingNativeMemory.getByte(30);
124                capsense_slider = backingNativeMemory.getByte(31);
125                capsense_proximity = backingNativeMemory.getByte(32);
126            }
127    
128            public void write() {
129                backingNativeMemory.setByte(0, api_version);
130                backingNativeMemory.setByte(1, fw_version);
131                backingNativeMemory.setShorts(2, analog, 0, analog.length);
132                backingNativeMemory.setShort(18, digital);
133                backingNativeMemory.setShorts(20, accel, 0, accel.length);
134                backingNativeMemory.setShorts(26, quad, 0, quad.length);
135                backingNativeMemory.setByte(30, buttons);
136                backingNativeMemory.setByte(31, capsense_slider);
137                backingNativeMemory.setByte(32, capsense_proximity);
138            }
139    
140            public int size() {
141                return size;
142            }
143        }   //data from IO (33 bytes)
144    
145        class status_block_t extends FRCControl.DynamicControlData {
146    
147            byte size = 25; // Must be 25 (size remaining in the block not counting the size variable)
148            byte id = kOutputBlockID; // Must be 18
149            output_t data;
150            byte flags;
151    
152            {
153                allocateMemory();
154                data = new output_t(
155                        new Pointer(backingNativeMemory.address().toUWord().toPrimitive() + 2,
156                        output_t.size));
157            }
158    
159            public void read() {
160    
161                size = backingNativeMemory.getByte(0);
162                id = backingNativeMemory.getByte(1);
163                data.read();
164                flags = backingNativeMemory.getByte(25);
165            }
166    
167            public void write() {
168                backingNativeMemory.setByte(0, size);
169                backingNativeMemory.setByte(1, id);
170                data.write();
171                backingNativeMemory.setByte(25, flags);
172            }
173    
174            public int size() {
175                return 26;
176            }
177    
178            public void copy(status_block_t dest) {
179                write();
180                Pointer.copyBytes(backingNativeMemory, 0, dest.backingNativeMemory, 0, size());
181                dest.read();
182            }
183        }
184    
185        class control_block_t extends FRCControl.DynamicControlData {
186    
187            byte size = 34; // Must be 34
188            byte id = kInputBlockID; // Must be 17
189            input_t data;
190    
191            {
192                allocateMemory();
193                data = new input_t(
194                        new Pointer(backingNativeMemory.address().toUWord().toPrimitive() + 2,
195                        input_t.size));
196            }
197    
198            public void read() {
199                size = backingNativeMemory.getByte(0);
200                id = backingNativeMemory.getByte(1);
201                data.read();
202            }
203    
204            public void write() {
205                backingNativeMemory.setByte(0, size);
206                backingNativeMemory.setByte(1, id);
207                data.write();
208            }
209    
210            public int size() {
211                return 35;
212            }
213    
214            public void copy(control_block_t dest) {
215                write();
216                Pointer.copyBytes(backingNativeMemory, 0, dest.backingNativeMemory, 0, size());
217                dest.read();
218            }
219        }
220    
221        public static class EnhancedIOException extends Exception {
222    
223            public EnhancedIOException(String msg) {
224                super(msg);
225            }
226        }
227        public static final double kAnalogInputResolution = ((double) ((1 << 14) - 1));
228        public static final double kAnalogInputReference = 3.3;
229        public static final double kAnalogOutputResolution = ((double) ((1 << 8) - 1));
230        public static final double kAnalogOutputReference = 4.0;
231        public static final double kAccelOffset = 8300;
232        public static final double kAccelScale = 3300.0;
233        public static final int kSupportedAPIVersion = 1;
234        control_block_t m_inputData;
235        status_block_t m_outputData;
236        final Object m_inputDataSemaphore;
237        final Object m_outputDataSemaphore;
238        boolean m_inputValid;
239        boolean m_outputValid;
240        boolean m_configChanged;
241        boolean m_requestEnhancedEnable;
242        short[] m_encoderOffsets = new short[2];
243    
244        /**
245         * Digital configuration for enhanced IO
246         */
247        public static class tDigitalConfig {
248    
249            /**
250             * The integer value representing this enumeration
251             */
252            public final int value;
253            static final int kUnknown_val = 0;
254            static final int kInputFloating_val = 1;
255            static final int kInputPullUp_val = 2;
256            static final int kInputPullDown_val = 3;
257            static final int kOutput_val = 4;
258            static final int kPWM_val = 5;
259            static final int kAnalogComparator_val = 6;
260            public static final tDigitalConfig kUnknown = new tDigitalConfig(kUnknown_val);
261            public static final tDigitalConfig kInputFloating = new tDigitalConfig(kInputFloating_val);
262            public static final tDigitalConfig kInputPullUp = new tDigitalConfig(kInputPullUp_val);
263            public static final tDigitalConfig kInputPullDown = new tDigitalConfig(kInputPullDown_val);
264            public static final tDigitalConfig kOutput = new tDigitalConfig((kOutput_val));
265            public static final tDigitalConfig kPWM = new tDigitalConfig((kPWM_val));
266            public static final tDigitalConfig kAnalogComparator = new tDigitalConfig((kAnalogComparator_val));
267    
268            private tDigitalConfig(int value) {
269                this.value = value;
270            }
271        }
272    
273        /**
274         * Accelerometer channel for enhanced IO
275         */
276        public static class tAccelChannel {
277    
278            /**
279             * The integer value representing this enumeration
280             */
281            public final int value;
282            static final int kAccelX_val = 0;
283            static final int kAccelY_val = 1;
284            static final int kAccelZ_val = 2;
285            public static final tAccelChannel kAccelX = new tAccelChannel(kAccelX_val);
286            public static final tAccelChannel kAccelY = new tAccelChannel(kAccelY_val);
287            public static final tAccelChannel kAccelZ = new tAccelChannel(kAccelZ_val);
288    
289            private tAccelChannel(int value) {
290                this.value = value;
291            }
292        }
293    
294        /**
295         * PWM period channels for enhanced IO
296         */
297        public static class tPWMPeriodChannels {
298    
299            /**
300             * The integer value representing this enumeration
301             */
302            public final int value;
303            static final int kPWMChannels1and2_val = 0;
304            static final int kPWMChannels3and4_val = 1;
305            public static final tPWMPeriodChannels kPWMChannels1and2 = new tPWMPeriodChannels(kPWMChannels1and2_val);
306            public static final tPWMPeriodChannels kPWMChannels3and4 = new tPWMPeriodChannels(kPWMChannels3and4_val);
307    
308            private tPWMPeriodChannels(int value) {
309                this.value = value;
310            }
311        }
312        static final byte kInputBlockID = 17, kOutputBlockID = 18;
313        static final int kStatusValid = 0x01, kStatusConfigChanged = 0x02, kForceEnhancedMode = 0x04;
314    
315        /**
316         * DriverStationEnhancedIO constructor.
317         *
318         * This is only called once when the DriverStation constructor is called.
319         */
320        DriverStationEnhancedIO() {
321            m_inputValid = false;
322            m_outputValid = false;
323            m_configChanged = false;
324            m_requestEnhancedEnable = false;
325            m_inputData = new control_block_t();
326            m_outputData = new status_block_t();
327            m_outputData.size = (byte) (m_outputData.size() - 1);
328            m_outputData.id = kOutputBlockID;
329            // Expected to be active low, so initialize inactive.
330            m_outputData.data.fixed_digital_out = 0x3;
331            m_inputDataSemaphore = new Object();
332            m_outputDataSemaphore = new Object();
333            m_encoderOffsets[0] = 0;
334            m_encoderOffsets[1] = 0;
335        }
336        status_block_t tempOutputData = new status_block_t();
337        control_block_t tempInputData = new control_block_t();
338    
339        /**
340         * Called by the DriverStation class when data is available.
341         * This function will set any modified configuration / output,
342         * then read the input and configuration from the IO.
343         */
344        void updateData() {
345            int retVal;
346            synchronized (m_outputDataSemaphore) {
347                if (m_outputValid || m_configChanged || m_requestEnhancedEnable) {
348                    m_outputData.flags = kStatusValid;
349                    if (m_requestEnhancedEnable) {
350                        // Someone called one of the get config APIs, but we are not in enhanced mode.
351                        m_outputData.flags |= kForceEnhancedMode;
352                    }
353                    if (m_configChanged) {
354                        if (!m_outputValid) {
355                            // Someone called one of the set config APIs, but we are not in enhanced mode.
356                            m_outputData.flags |= kForceEnhancedMode;
357                        }
358                        m_outputData.flags |= kStatusConfigChanged;
359                    }
360                    FRCControl.overrideIOConfig(m_outputData, 5);
361                }
362                retVal = FRCControl.getDynamicControlData(kOutputBlockID, tempOutputData, tempOutputData.size(), 5);
363                if (retVal == 0) {
364                    if (m_outputValid) {
365                        if (m_configChanged) {
366                            // If our config change made the round trip then clear the flag.
367                            if (isConfigEqual(tempOutputData, m_outputData)) {
368                                m_configChanged = false;
369                            }
370                        } else {
371                            // TODO: This won't work until artf1128 is fixed
372                            //if (tempOutputData.flags & kStatusConfigChanged)
373                            {
374                                // Configuration was updated on the DS, so update our local cache.
375                                mergeConfigIntoOutput(tempOutputData, m_outputData);
376                            }
377                        }
378                    } else {
379                        // Initialize the local cache.
380                        mergeConfigIntoOutput(tempOutputData, m_outputData);
381                    }
382                    m_requestEnhancedEnable = false;
383                    m_outputValid = true;
384                } else {
385                    m_outputValid = false;
386                    m_inputValid = false;
387                }
388            }
389    
390            synchronized (m_inputDataSemaphore) {
391    
392                retVal = FRCControl.getDynamicControlData(kInputBlockID, tempInputData, tempInputData.size(), 5);
393                if (retVal == 0 && tempInputData.data.api_version == kSupportedAPIVersion) {
394                    tempInputData.copy(m_inputData);
395                    m_inputValid = true;
396                } else {
397                    m_outputValid = false;
398                    m_inputValid = false;
399                }
400            }
401        }
402    
403        /**
404         * Merge the config portion of the DS output block into the local cache.
405         */
406        void mergeConfigIntoOutput(status_block_t dsOutputBlock, status_block_t localCache) {
407            localCache.data.digital = (short) ((localCache.data.digital & dsOutputBlock.data.digital_oe) |
408                    (dsOutputBlock.data.digital & ~dsOutputBlock.data.digital_oe));
409            localCache.data.digital_oe = dsOutputBlock.data.digital_oe;
410            localCache.data.digital_pe = dsOutputBlock.data.digital_pe;
411            localCache.data.pwm_period[0] = dsOutputBlock.data.pwm_period[0];
412            localCache.data.pwm_period[1] = dsOutputBlock.data.pwm_period[1];
413            localCache.data.setEnables(dsOutputBlock.data.getEnables());
414        }
415    
416        /**
417         * Compare the config portion of the output blocks.
418         */
419        boolean isConfigEqual(status_block_t dsOutputBlock, status_block_t localCache) {
420            if (localCache.data.digital_oe != dsOutputBlock.data.digital_oe) {
421                return false;
422            }
423            if ((localCache.data.digital & ~dsOutputBlock.data.digital) !=
424                    (dsOutputBlock.data.digital & ~dsOutputBlock.data.digital)) {
425                return false;
426            }
427            if (localCache.data.digital_pe != dsOutputBlock.data.digital_pe) {
428                return false;
429            }
430            if (localCache.data.pwm_period[0] != dsOutputBlock.data.pwm_period[0]) {
431                return false;
432            }
433            if (localCache.data.pwm_period[1] != dsOutputBlock.data.pwm_period[1]) {
434                return false;
435            }
436            if (localCache.data.getEnables() != dsOutputBlock.data.getEnables()) {
437                return false;
438            }
439            return true;
440        }
441    
442        /**
443         * Query an accelerometer channel on the DS IO.
444         *
445         * @param channel The channel number to read.
446         * @return The current acceleration on the channel in Gs.
447         */
448        public double getAcceleration(tAccelChannel channel) throws EnhancedIOException {
449            if (!m_inputValid) {
450                throw new EnhancedIOException("Enhanced IO Missing");
451            }
452            synchronized (m_inputDataSemaphore) {
453                return (m_inputData.data.accel[channel.value] - kAccelOffset) / kAccelScale;
454            }
455        }
456    
457        /**
458         * Query an analog input channel on the DS IO.
459         *
460         * @param channel The channel number to read. [1,8]
461         * @return The analog input voltage for the channel.
462         */
463        public double getAnalogIn(int channel) throws EnhancedIOException {
464            // 3.3V is the analog reference voltage
465            return getAnalogInRatio(channel) * kAnalogInputReference;
466        }
467    
468        /**
469         * Query an analog input channel on the DS IO in ratiometric form.
470         *
471         * @param channel The channel number to read. [1,8]
472         * @return The analog input percentage for the channel.
473         */
474        public double getAnalogInRatio(int channel) throws EnhancedIOException {
475            BoundaryException.assertWithinBounds(channel, 1, 8);
476            if (!m_inputValid) {
477                throw new EnhancedIOException("Enhanced IO Missing");
478            }
479            synchronized (m_inputDataSemaphore) {
480                return m_inputData.data.analog[channel - 1] / kAnalogInputResolution;
481            }
482        }
483    
484        /**
485         * Query the voltage currently being output.
486         *
487         * AO1 is pin 11 on the top connector (P2).
488         * AO2 is pin 12 on the top connector (P2).
489         *
490         * @param channel The analog output channel on the DS IO. [1,2]
491         * @return The voltage being output on the channel.
492         */
493        public double getAnalogOut(int channel) throws EnhancedIOException {
494            BoundaryException.assertWithinBounds(channel, 1, 2);
495            if (!m_outputValid) {
496                throw new EnhancedIOException("Enhanced IO Missing");
497            }
498    
499            synchronized (m_outputDataSemaphore) {
500                int tempData = m_outputData.data.dac[channel - 1];
501                tempData = tempData < 0 ? tempData + 256 : tempData;
502                return tempData * kAnalogOutputReference / kAnalogOutputResolution;
503            }
504        }
505    
506        /**
507         * Set the analog output voltage.
508         *
509         * AO1 is pin 11 on the top connector (P2).
510         * AO2 is pin 12 on the top connector (P2).
511         * AO1 is the reference voltage for the 2 analog comparators on DIO15 and DIO16.
512         *
513         * The output range is 0V to 4V, however due to the supply voltage don't expect more than about 3V.
514         * Current supply capability is only 100uA.
515         *
516         * @param channel The analog output channel on the DS IO. [1,2]
517         * @param value The voltage to output on the channel.
518         */
519        public void setAnalogOut(int channel, double value) throws EnhancedIOException {
520            BoundaryException.assertWithinBounds(channel, 1, 2);
521            if (!m_outputValid) {
522                throw new EnhancedIOException("Enhanced IO Missing");
523            }
524            if (value < 0.0) {
525                value = 0.0;
526            }
527            if (value > kAnalogOutputReference) {
528                value = kAnalogOutputReference;
529            }
530            if (value > kAnalogOutputReference) {
531                value = kAnalogOutputReference;
532            }
533    
534            synchronized (m_outputDataSemaphore) {
535                m_outputData.data.dac[channel - 1] = (byte) (value / kAnalogOutputReference * kAnalogOutputResolution);
536            }
537        }
538    
539        /**
540         * Get the state of a button on the IO board.
541         *
542         * Button1 is the physical button "S1".
543         * Button2 is pin 4 on the top connector (P2).
544         * Button3 is pin 6 on the top connector (P2).
545         * Button4 is pin 8 on the top connector (P2).
546         * Button5 is pin 10 on the top connector (P2).
547         * Button6 is pin 7 on the top connector (P2).
548         *
549         * Button2 through Button6 are Capacitive Sense buttons.
550         *
551         * @param channel The button channel to read. [1,6]
552         * @return The state of the selected button.
553         */
554        public boolean getButton(int channel) throws EnhancedIOException {
555            BoundaryException.assertWithinBounds(channel, 1, 6);
556            return ((getButtons() >> (channel - 1)) & 1) != 0;
557        }
558    
559        /**
560         * Get the state of all the button channels.
561         *
562         * @return The state of the 6 button channels in the 6 lsb of the returned byte.
563         */
564        public byte getButtons() throws EnhancedIOException {
565            if (!m_inputValid) {
566                throw new EnhancedIOException("Enhanced IO Missing");
567            }
568            synchronized (m_inputDataSemaphore) {
569                return m_inputData.data.buttons;
570            }
571        }
572    
573        /**
574         * Set the state of an LED on the IO board.
575         *
576         * @param channel The LED channel to set. [1,8]
577         * @param value True to turn the LED on.
578         */
579        public void setLED(int channel, boolean value) throws EnhancedIOException {
580            BoundaryException.assertWithinBounds(channel, 1, 8);
581            if (!m_outputValid) {
582                throw new EnhancedIOException("Enhanced IO Missing");
583            }
584            byte leds;
585            synchronized (m_outputDataSemaphore) {
586                leds = m_outputData.data.leds;
587    
588                leds &= ~(1 << (channel - 1));
589                if (value) {
590                    leds |= 1 << (channel - 1);
591                }
592    
593                m_outputData.data.leds = leds;
594            }
595        }
596    
597        /**
598         * Set the state of all 8 LEDs on the IO board.
599         *
600         * @param value The state of each LED.  LED1 is lsb and LED8 is msb.
601         */
602        public void setLEDs(byte value) throws EnhancedIOException {
603            if (!m_outputValid) {
604                throw new EnhancedIOException("Enhanced IO Missing");
605            }
606            synchronized (m_outputDataSemaphore) {
607                m_outputData.data.leds = value;
608            }
609        }
610    
611        /**
612         * Get the current state of a DIO channel regardless of mode.
613         *
614         * @param channel The DIO channel to read. [1,16]
615         * @return The state of the selected digital line.
616         */
617        public boolean getDigital(int channel) throws EnhancedIOException {
618            BoundaryException.assertWithinBounds(channel, 1, 16);
619            return ((getDigitals() >> (channel - 1)) & 1) != 0;
620        }
621    
622        /**
623         * Get the state of all 16 DIO lines regardless of mode.
624         *
625         * @return The state of all DIO lines. DIO1 is lsb and DIO16 is msb.
626         */
627        public short getDigitals() throws EnhancedIOException {
628            if (!m_inputValid) {
629                throw new EnhancedIOException("Enhanced IO Missing");
630            }
631            synchronized (m_inputDataSemaphore) {
632                return m_inputData.data.digital;
633            }
634        }
635    
636        /**
637         * Set the state of a DIO line that is configured for digital output.
638         *
639         * @param channel The DIO channel to set. [1,16]
640         * @param value The state to set the selected channel to.
641         */
642        public void setDigitalOutput(int channel, boolean value) throws EnhancedIOException {
643            BoundaryException.assertWithinBounds(channel, 1, 16);
644            if (!m_outputValid) {
645                throw new EnhancedIOException("Enhanced IO Missing");
646            }
647            short digital;
648            synchronized (m_outputDataSemaphore) {
649    
650                if ((m_outputData.data.digital_oe & (1 << (channel - 1))) != 0) {
651                    digital = m_outputData.data.digital;
652    
653                    digital &= ~(1 << (channel - 1));
654                    if (value) {
655                        digital |= 1 << (channel - 1);
656                    }
657    
658                    m_outputData.data.digital = digital;
659                } else {
660                    System.err.println("Line not configured for output");
661                }
662            }
663        }
664    
665        /**
666         * Get the current configuration for a DIO line.
667         *
668         * This has the side effect of forcing the Driver Station to switch to Enhanced mode if it's not when called.
669         * If Enhanced mode is not enabled when this is called, it will return kUnknown.
670         *
671         * @param channel The DIO channel config to get. [1,16]
672         * @return The configured mode for the DIO line.
673         */
674        public tDigitalConfig getDigitalConfig(int channel) throws EnhancedIOException {
675            BoundaryException.assertWithinBounds(channel, 1, 16);
676            if (!m_outputValid) {
677                m_requestEnhancedEnable = true;
678                throw new EnhancedIOException("Enhanced IO Missing");
679            }
680            synchronized (m_outputDataSemaphore) {
681                if ((channel >= 1) && (channel <= 4)) {
682                    if ((m_outputData.data.pwm_enable & (1 << (channel - 1))) != 0) {
683                        return tDigitalConfig.kPWM;
684                    }
685                }
686                if ((channel >= 15) && (channel <= 16)) {
687                    if ((m_outputData.data.comparator_enable & (1 << (channel - 15))) != 0) {
688                        return tDigitalConfig.kAnalogComparator;
689                    }
690                }
691                if ((m_outputData.data.digital_oe & (1 << (channel - 1))) != 0) {
692                    return tDigitalConfig.kOutput;
693                }
694                if ((m_outputData.data.digital_pe & (1 << (channel - 1))) == 0) {
695                    return tDigitalConfig.kInputFloating;
696                }
697                if ((m_outputData.data.digital & (1 << (channel - 1))) != 0) {
698                    return tDigitalConfig.kInputPullUp;
699                } else {
700                    return tDigitalConfig.kInputPullDown;
701                }
702            }
703        }
704    
705        /**
706         * Override the DS's configuration of a DIO line.
707         *
708         * If configured to kInputFloating, the selected DIO line will be tri-stated with no internal pull resistor.
709         *
710         * If configured to kInputPullUp, the selected DIO line will be tri-stated with a 5k-Ohm internal pull-up resistor enabled.
711         *
712         * If configured to kInputPullDown, the selected DIO line will be tri-stated with a 5k-Ohm internal pull-down resistor enabled.
713         *
714         * If configured to kOutput, the selected DIO line will actively drive to 0V or Vddio (specified by J1 and J4).
715         * DIO1 through DIO12, DIO15, and DIO16 can source 4mA and can sink 8mA.
716         * DIO12 and DIO13 can source 4mA and can sink 25mA.
717         *
718         * In addition to the common configurations, DIO1 through DIO4 can be configured to kPWM to enable PWM output.
719         *
720         * In addition to the common configurations, DIO15 and DIO16 can be configured to kAnalogComparator to enable
721         * analog comparators on those 2 DIO lines.  When enabled, the lines are tri-stated and will accept analog voltages
722         * between 0V and 3.3V.  If the input voltage is greater than the voltage output by AO1, the DIO will read as true,
723         * if less then false.
724         *
725         * @param channel The DIO line to configure. [1,16]
726         * @param config The mode to put the DIO line in.
727         */
728        public void setDigitalConfig(int channel, tDigitalConfig config) throws EnhancedIOException {
729            BoundaryException.assertWithinBounds(channel, 1, 16);
730            if (config == tDigitalConfig.kPWM && ((channel > 4) || (channel < 1))) {
731                throw new EnhancedIOException("PWM channels must be between 1 and 4");
732            }
733            if (config == tDigitalConfig.kAnalogComparator && ((channel < 15) || (channel > 16))) {
734                throw new EnhancedIOException("Analog comparator channels must be between 15 and 16");
735            }
736    
737            synchronized (m_outputDataSemaphore) {
738                m_configChanged = true;
739    
740                if ((channel >= 1) && (channel <= 4)) {
741                    if (config == tDigitalConfig.kPWM) {
742                        m_outputData.data.pwm_enable |= 1 << (channel - 1);
743                        m_outputData.data.digital &= ~(1 << (channel - 1));
744                        m_outputData.data.digital_oe |= 1 << (channel - 1);
745                        m_outputData.data.digital_pe &= ~(1 << (channel - 1));
746                        return;
747                    } else {
748                        m_outputData.data.pwm_enable &= ~(1 << (channel - 1));
749                    }
750                } else if ((channel >= 15) && (channel <= 16)) {
751                    if (config == tDigitalConfig.kAnalogComparator) {
752                        m_outputData.data.comparator_enable |= 1 << (channel - 15);
753                        m_outputData.data.digital &= ~(1 << (channel - 1));
754                        m_outputData.data.digital_oe &= ~(1 << (channel - 1));
755                        m_outputData.data.digital_pe &= ~(1 << (channel - 1));
756                        return;
757                    } else {
758                        m_outputData.data.comparator_enable &= ~(1 << (channel - 15));
759                    }
760                }
761                if (config == tDigitalConfig.kInputFloating) {
762                    m_outputData.data.digital &= ~(1 << (channel - 1));
763                    m_outputData.data.digital_oe &= ~(1 << (channel - 1));
764                    m_outputData.data.digital_pe &= ~(1 << (channel - 1));
765                } else if (config == tDigitalConfig.kInputPullUp) {
766                    m_outputData.data.digital |= 1 << (channel - 1);
767                    m_outputData.data.digital_oe &= ~(1 << (channel - 1));
768                    m_outputData.data.digital_pe |= 1 << (channel - 1);
769                } else if (config == tDigitalConfig.kInputPullDown) {
770                    m_outputData.data.digital &= ~(1 << (channel - 1));
771                    m_outputData.data.digital_oe &= ~(1 << (channel - 1));
772                    m_outputData.data.digital_pe |= 1 << (channel - 1);
773                } else if (config == tDigitalConfig.kOutput) {
774                    m_outputData.data.digital_oe |= 1 << (channel - 1);
775                    m_outputData.data.digital_pe &= ~(1 << (channel - 1));
776                } else {
777                    // Something went wrong.
778                }
779            }
780        }
781    
782        /**
783         * Get the period of a PWM generator.
784         *
785         * This has the side effect of forcing the Driver Station to switch to Enhanced mode if it's not when called.
786         * If Enhanced mode is not enabled when this is called, it will return 0.
787         *
788         * @param channels Select the generator by specifying the two channels to which it is connected.
789         * @return The period of the PWM generator in seconds.
790         */
791        public double getPWMPeriod(tPWMPeriodChannels channels) throws EnhancedIOException {
792            if (!m_outputValid) {
793                m_requestEnhancedEnable = true;
794                throw new EnhancedIOException("Enhanced IO Missing");
795            }
796            synchronized (m_outputDataSemaphore) {
797                int tempData = m_outputData.data.pwm_period[channels.value] & 0xFFFF;
798                return  tempData / 24000000.0;
799            }
800        }
801    
802        /**
803         * Set the period of a PWM generator.
804         *
805         * There are 2 PWM generators on the IO board.  One can generate PWM signals on DIO1 and DIO2,
806         * the other on DIO3 and DIO4.  Each generator has one counter and two compare registers.  As such,
807         * each pair of PWM outputs share the output period but have independent duty cycles.
808         *
809         * @param channels Select the generator by specifying the two channels to which it is connected.
810         * @param period The period of the PWM generator in seconds. [0.0,0.002731]
811         */
812        public void setPWMPeriod(tPWMPeriodChannels channels, double period) throws EnhancedIOException {
813            // Convert to ticks based on the IO board's 24MHz clock
814            double ticks = period * 24000000.0;
815            // Limit the range of the ticks... warn if too big.
816            if (ticks > 65534.0) {
817                ticks = 65534.0;
818                throw new EnhancedIOException("Enhanced IO PWM Period Out of Range");
819            } else if (ticks < 0.0) {
820                ticks = 0.0;
821            }
822            // Preserve the duty cycles.
823            double[] dutyCycles = new double[2];
824            dutyCycles[0] = getPWMOutput((channels.value << 1) + 1);
825            dutyCycles[1] = getPWMOutput((channels.value << 1) + 2);
826            synchronized (m_outputDataSemaphore) {
827                // Update the period
828                m_outputData.data.pwm_period[channels.value] = (short) ticks;
829                m_configChanged = true;
830            }
831            // Restore the duty cycles
832            setPWMOutput((channels.value << 1) + 1, dutyCycles[0]);
833            setPWMOutput((channels.value << 1) + 2, dutyCycles[1]);
834        }
835    
836        /**
837         * Get the state being output on a fixed digital output.
838         *
839         * @param channel The FixedDO line to get. [1,2]
840         * @return The state of the FixedDO line.
841         */
842        public boolean getFixedDigitalOutput(int channel) throws EnhancedIOException {
843            BoundaryException.assertWithinBounds(channel, 1, 2);
844            if (!m_outputValid) {
845                throw new EnhancedIOException("Enhanced IO Missing");
846            }
847            synchronized (m_outputDataSemaphore) {
848                return ((m_outputData.data.fixed_digital_out >> (channel - 1)) & 1) != 0;
849            }
850        }
851    
852        /**
853         * Set the state to output on a Fixed High Current Digital Output line.
854         *
855         * FixedDO1 is pin 5 on the top connector (P2).
856         * FixedDO2 is pin 3 on the top connector (P2).
857         *
858         * The FixedDO lines always output 0V and 3.3V regardless of J1 and J4.
859         * They can source 4mA and can sink 25mA.  Because of this, they are expected to be used
860         * in an active low configuration, such as connecting to the cathode of a bright LED.
861         * Because they are expected to be active low, they default to true.
862         *
863         * @param channel The FixedDO channel to set.
864         * @param value The state to set the FixedDO.
865         */
866        public void setFixedDigitalOutput(int channel, boolean value) throws EnhancedIOException {
867            BoundaryException.assertWithinBounds(channel, 1, 2);
868            if (!m_outputValid) {
869                throw new EnhancedIOException("Enhanced IO Missing");
870            }
871            byte digital;
872            synchronized (m_outputDataSemaphore) {
873                digital = m_outputData.data.fixed_digital_out;
874    
875                digital &= ~(1 << (channel - 1));
876                if (value) {
877                    digital |= 1 << (channel - 1);
878                }
879    
880                m_outputData.data.fixed_digital_out = digital;
881            }
882        }
883    
884        /**
885         * Get the position of a quadrature encoder.
886         *
887         * There are two signed 16-bit 4X quadrature decoders on the IO board.  These decoders are always monitoring
888         * the state of the lines assigned to them, but these lines do not have to be used for encoders.
889         *
890         * Encoder1 uses DIO4 for "A", DIO6 for "B", and DIO8 for "Index".
891         * Encoder2 uses DIO5 for "A", DIO7 for "B", and DIO9 for "Index".
892         *
893         * The index functionality can be enabled or disabled using SetEncoderIndexEnable().
894         *
895         * @param encoderNumber The quadrature encoder to access. [1,2]
896         * @return The current position of the quadrature encoder.
897         */
898        public short getEncoder(int encoderNumber) throws EnhancedIOException {
899            BoundaryException.assertWithinBounds(encoderNumber, 1, 2);
900            if (!m_inputValid) {
901                throw new EnhancedIOException("Enhanced IO Missing");
902            }
903            synchronized (m_inputDataSemaphore) {
904                return (short) (m_inputData.data.quad[encoderNumber - 1] - m_encoderOffsets[encoderNumber - 1]);
905            }
906        }
907    
908        /**
909         * Reset the position of an encoder to 0.
910         *
911         * This simply stores an offset locally.  It does not reset the hardware counter on the IO board.
912         * If you use this method with Index enabled, you may get unexpected results.
913         *
914         * @param encoderNumber The quadrature encoder to reset. [1,2]
915         */
916        public void resetEncoder(int encoderNumber) throws EnhancedIOException {
917            BoundaryException.assertWithinBounds(encoderNumber, 1, 2);
918            if (!m_inputValid) {
919                throw new EnhancedIOException("Enhanced IO Missing");
920            }
921            synchronized (m_inputDataSemaphore) {
922                m_encoderOffsets[encoderNumber - 1] = m_inputData.data.quad[encoderNumber - 1];
923            }
924        }
925    
926        /**
927         * Get the current configuration of a quadrature encoder index channel.
928         *
929         * This has the side effect of forcing the Driver Station to switch to Enhanced mode if it's not when called.
930         * If Enhanced mode is not enabled when this is called, it will return false.
931         *
932         * @param encoderNumber The quadrature encoder. [1,2]
933         * @return Is the index channel of the encoder enabled.
934         */
935        public boolean getEncoderIndexEnable(int encoderNumber) throws EnhancedIOException {
936            BoundaryException.assertWithinBounds(encoderNumber, 1, 2);
937            if (!m_outputValid) {
938                m_requestEnhancedEnable = true;
939                throw new EnhancedIOException("Enhanced IO Missing");
940            }
941            synchronized (m_outputDataSemaphore) {
942                return ((m_outputData.data.quad_index_enable >> (encoderNumber - 1)) & 1) != 0;
943            }
944        }
945    
946        /**
947         * Enable or disable the index channel of a quadrature encoder.
948         *
949         * The quadrature decoders on the IO board support an active-low index input.
950         *
951         * Encoder1 uses DIO8 for "Index".
952         * Encoder2 uses DIO9 for "Index".
953         *
954         * When enabled, the decoder's counter will be reset to 0 when A, B, and Index are all low.
955         *
956         * @param encoderNumber The quadrature encoder. [1,2]
957         * @param enable If true, reset the encoder in an index condition.
958         */
959        public void setEncoderIndexEnable(int encoderNumber, boolean enable) {
960            BoundaryException.assertWithinBounds(encoderNumber, 1, 2);
961            synchronized (m_outputDataSemaphore) {
962                m_outputData.data.quad_index_enable &= ~(1 << (encoderNumber - 1));
963                if (enable) {
964                    m_outputData.data.quad_index_enable |= 1 << (encoderNumber - 1);
965                }
966                m_configChanged = true;
967            }
968        }
969    
970        /**
971         * Get the value of the Capacitive Sense touch slider.
972         *
973         * @return Value between 0.0 (toward center of board) and 1.0 (toward edge of board).  -1.0 means no touch detected.
974         */
975        public double getTouchSlider() throws EnhancedIOException {
976            if (!m_inputValid) {
977                throw new EnhancedIOException("Enhanced IO Missing");
978            }
979            synchronized (m_inputDataSemaphore) {
980                byte rawValue = m_inputData.data.capsense_slider;
981                int value = rawValue < 0 ? rawValue + 256 : rawValue;
982                return value == 255 ? -1.0 : value / 254.0;
983            }
984        }
985    
986        /**
987         * Get the percent duty-cycle that the PWM generator channel is configured to output.
988         *
989         * @param channel The DIO line's PWM generator to get the duty-cycle from. [1,4]
990         * @return The percent duty-cycle being output (if the DIO line is configured for PWM). [0.0,1.0]
991         */
992        public double getPWMOutput(int channel) throws EnhancedIOException {
993            BoundaryException.assertWithinBounds(channel, 1, 4);
994            if (!m_outputValid) {
995                throw new EnhancedIOException("Enhanced IO Missing");
996            }
997            synchronized (m_outputDataSemaphore) {
998                int tempCompare = m_outputData.data.pwm_compare[channel - 1] & 0xFFFF;
999                int tempPeriod = m_outputData.data.pwm_period[(channel - 1) >> 1] & 0xFFFF;
1000                return (double) tempCompare / (double) tempPeriod;
1001            }
1002        }
1003    
1004        /**
1005         * Set the percent duty-cycle to output on a PWM enabled DIO line.
1006         *
1007         * DIO1 through DIO4 have the ability to output a PWM signal.  The period of the
1008         * signal can be configured in pairs using SetPWMPeriod().
1009         *
1010         * @param channel The DIO line's PWM generator to set. [1,4]
1011         * @param value The percent duty-cycle to output from the PWM generator. [0.0,1.0]
1012         */
1013        public void setPWMOutput(int channel, double value) throws EnhancedIOException {
1014            BoundaryException.assertWithinBounds(channel, 1, 4);
1015            if (!m_outputValid) {
1016                throw new EnhancedIOException("Enhanced IO Missing");
1017            }
1018            if (value > 1.0) {
1019                value = 1.0;
1020            } else if (value < 0.0) {
1021                value = 0.0;
1022            }
1023            synchronized (m_outputDataSemaphore) {
1024                m_outputData.data.pwm_compare[channel - 1] = (short) (value * (double) m_outputData.data.pwm_period[(channel - 1) >> 1]);
1025            }
1026        }
1027    
1028        /**
1029         * Get the firmware version running on the IO board.
1030         *
1031         * This also has the side effect of forcing the driver station to switch to Enhanced mode if it is not.
1032         * If you plan to switch between Driver Stations with unknown IO configurations, you can call this
1033         * until it returns a non-0 version to ensure that this API is accessible before proceeding.
1034         *
1035         * @return The version of the firmware running on the IO board.  0 if the board is not attached or not in Enhanced mode.
1036         */
1037        public byte getFirmwareVersion() throws EnhancedIOException {
1038            if (!m_inputValid) {
1039                m_requestEnhancedEnable = true;
1040                throw new EnhancedIOException("Enhanced IO Missing");
1041            }
1042            synchronized (m_inputDataSemaphore) {
1043                return m_inputData.data.fw_version;
1044            }
1045        }
1046    }