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.squawk.util.MathUtils;
010    import edu.wpi.first.wpilibj.communication.ModulePresence;
011    import edu.wpi.first.wpilibj.fpga.tDIO;
012    import edu.wpi.first.wpilibj.util.AllocationException;
013    import edu.wpi.first.wpilibj.util.CheckedAllocationException;
014    
015    /**
016     * Class representing a digital module
017     * @author dtjones
018     */
019    public class DigitalModule extends Module {
020    
021        /**
022         * Expected loop timing
023         */
024        public static final int kExpectedLoopTiming = 260;
025        private static final Resource DIOChannels = new Resource(tDIO.kNumSystems * SensorBase.kDigitalChannels);
026        private static final Resource DO_PWMGenerators[] = new Resource[tDIO.kNumSystems];
027        tDIO m_fpgaDIO;
028        private final Object syncRoot = new Object();
029    
030        /**
031         * Get an instance of an Digital Module.
032         * Singleton digital module creation where a module is allocated on the first use
033         * and the same module is returned on subsequent uses.
034         *
035         * @param moduleNumber The number of the digital module to access.
036         * @return The digital module of the specified number.
037         */
038        public static synchronized DigitalModule getInstance(final int moduleNumber) {
039            SensorBase.checkDigitalModule(moduleNumber);
040            return (DigitalModule) getModule(ModulePresence.ModuleType.kDigital, moduleNumber);
041        }
042    
043        /**
044         * Convert a channel to its fpga reference
045         * @param channel the channel to convert
046         * @return the converted channel
047         */
048        public static int remapDigitalChannel(final int channel) {
049            return 15 - channel;
050        }
051    
052        /**
053         * Convert a channel from it's fpga reference
054         * @param channel the channel to convert
055         * @return the converted channel
056         */
057        public static int unmapDigitalChannel(final int channel) {
058            return 15 - channel;
059        }
060    
061        /**
062         * Create a new digital module
063         * @param moduleNumber The number of the digital module to use (1 or 2)
064         */
065        protected DigitalModule(final int moduleNumber) {
066            super(ModulePresence.ModuleType.kDigital, moduleNumber);
067            
068            DO_PWMGenerators[m_moduleNumber - 1] = new Resource(tDIO.kDO_PWMDutyCycle_NumElements);
069            m_fpgaDIO = new tDIO(m_moduleNumber - 1);
070    
071            while (tDIO.readLoopTiming() == 0) {
072                Timer.delay(.001);
073            }
074    
075            if (tDIO.readLoopTiming() != kExpectedLoopTiming) {
076                System.out.print("DIO LoopTiming: ");
077                System.out.print(tDIO.readLoopTiming());
078                System.out.print(", expecting: ");
079                System.out.println(kExpectedLoopTiming);
080            }
081            
082            //Calculate the length, in ms, of one DIO loop
083            double loopTime = tDIO.readLoopTiming()/(kSystemClockTicksPerMicrosecond*1e3);
084            
085            tDIO.writePWMConfig_Period((short) (PWM.kDefaultPwmPeriod/loopTime + .5));
086    
087            //Calculate the minimum time for the PWM signal to be high by using the number of steps down from center
088            tDIO.writePWMConfig_MinHigh((short) ((PWM.kDefaultPwmCenter-PWM.kDefaultPwmStepsDown*loopTime)/loopTime + .5));
089    
090    
091            // Ensure that PWM output values are set to OFF
092            for (int pwm_index = 1; pwm_index <= kPwmChannels; pwm_index++) {
093                setPWM(pwm_index, PWM.kPwmDisabled);
094                setPWMPeriodScale(pwm_index, PWM.PeriodMultiplier.k4X_val); // Set all to 4x by default.
095            }
096    
097            // Turn off all relay outputs.
098            m_fpgaDIO.writeSlowValue_RelayFwd(0);
099            m_fpgaDIO.writeSlowValue_RelayRev(0);
100        }
101    
102        /**
103         * Set a PWM channel to the desired value. The values range from 0 to 255 and the period is controlled
104         * by the PWM Period and MinHigh registers.
105         *
106         * @param channel The PWM channel to set.
107         * @param value The PWM value to set.
108         */
109        public void setPWM(final int channel, final int value) {
110            checkPWMChannel(channel);
111            m_fpgaDIO.writePWMValue(channel - 1, value);
112        }
113    
114        /**
115         * Get a value from a PWM channel. The values range from 0 to 255.
116         *
117         * @param channel The PWM channel to read from.
118         * @return The raw PWM value.
119         */
120        public int getPWM(final int channel) {
121            checkPWMChannel(channel);
122            return m_fpgaDIO.readPWMValue(channel - 1);
123        }
124    
125        /**
126         * Set how how often the PWM signal is squelched, thus scaling the period.
127         *
128         * @param channel The PWM channel to configure.
129         * @param squelchMask The 2-bit mask of outputs to squelch.
130         */
131        public void setPWMPeriodScale(final int channel, final int squelchMask) {
132            checkPWMChannel(channel);
133            m_fpgaDIO.writePWMPeriodScale((byte) (channel - 1), squelchMask);
134        }
135    
136        /**
137         * Set the state of a relay.
138         * Set the state of a relay output to be forward. Relays have two outputs and each is
139         * independently set to 0v or 12v.
140         *
141         * @param channel The Relay channel.
142         * @param on Indicates whether to set the relay to the On state.
143         */
144        public void setRelayForward(final int channel, final boolean on) {
145            checkRelayChannel(channel);
146    
147            synchronized (syncRoot) {
148                int forwardRelays = m_fpgaDIO.readSlowValue_RelayFwd();
149                if (on) {
150                    forwardRelays |= 1 << (channel - 1);
151                } else {
152                    forwardRelays &= ~(1 << (channel - 1));
153                }
154                m_fpgaDIO.writeSlowValue_RelayFwd(forwardRelays);
155            }
156        }
157    
158        /**
159         * Set the state of a relay.
160         * Set the state of a relay output to be reverse. Relays have two outputs and each is
161         * independently set to 0v or 12v.
162         *
163         * @param channel The Relay channel.
164         * @param on Indicates whether to set the relay to the On state.
165         */
166        public void setRelayReverse(final int channel, final boolean on) {
167            SensorBase.checkRelayChannel(channel);
168    
169            synchronized (syncRoot) {
170                int reverseRelays = m_fpgaDIO.readSlowValue_RelayRev();
171                if (on) {
172                    reverseRelays |= 1 << (channel - 1);
173                } else {
174                    reverseRelays &= ~(1 << (channel - 1));
175                }
176                m_fpgaDIO.writeSlowValue_RelayRev(reverseRelays);
177            }
178        }
179    
180        /**
181         * Get the current state of the forward relay channel
182         * @param channel the channel of the relay to get
183         * @return The current state of the relay.
184         */
185        public boolean getRelayForward(int channel) {
186            int forwardRelays = m_fpgaDIO.readSlowValue_RelayFwd();
187            return (forwardRelays & (1 << (channel - 1))) != 0;
188        }
189    
190        /**
191         * Get the current state of all of the forward relay channels on this module.
192         * @return The state of all forward relay channels as a byte.
193         */
194        public byte getRelayForward() {
195            return (byte) m_fpgaDIO.readSlowValue_RelayFwd();
196        }
197    
198        /**
199         * Get the current state of the reverse relay channel
200         * @param channel the channel of the relay to get
201         * @return The current statte of the relay
202         */
203        public boolean getRelayReverse(int channel) {
204            int reverseRelays = m_fpgaDIO.readSlowValue_RelayRev();
205            return (reverseRelays & (1 << (channel - 1))) != 0;
206        }
207    
208        /**
209         * Get the current state of all of the reverse relay channels on this module.
210         * @return The state of all forward relay channels as a byte.
211         */
212        public byte getRelayReverse() {
213            return (byte) m_fpgaDIO.readSlowValue_RelayRev();
214        }
215    
216        /**
217         * Allocate Digital I/O channels.
218         * Allocate channels so that they are not accidently reused. Also the direction is set at the
219         * time of the allocation.
220         *
221         * @param channel The channel to allocate.
222         * @param input Indicates whether the I/O pin is an input (true) or an output (false).
223         * @return True if the I/O pin was allocated, false otherwise.
224         */
225        public boolean allocateDIO(final int channel, final boolean input) {
226            try {
227                DIOChannels.allocate((kDigitalChannels * (m_moduleNumber - 1) + channel - 1));
228            } catch (CheckedAllocationException e) {
229                throw new AllocationException(
230                        "Digital channel " + channel + " on module " + m_moduleNumber + " is already allocated");
231            }
232            final int outputEnable = m_fpgaDIO.readOutputEnable();
233            final int bitToSet = 1 << (DigitalModule.remapDigitalChannel((channel - 1)));
234            short outputEnableValue;
235    
236            if (input) {
237                outputEnableValue = (short) (outputEnable & (~bitToSet));
238            } else {
239                outputEnableValue = (short) (outputEnable | bitToSet);
240            }
241    
242            m_fpgaDIO.writeOutputEnable(outputEnableValue);
243            return true;
244        }
245    
246        /**
247         * Free the resource associated with a digital I/O channel.
248         *
249         * @param channel The channel whose resources should be freed.
250         */
251        public void freeDIO(final int channel) {
252            DIOChannels.free((kDigitalChannels * (m_moduleNumber - 1) + channel - 1));
253        }
254    
255        /**
256         * Write a digital I/O bit to the FPGA.
257         * Set a single value on a digital I/O channel.
258         *
259         * @param channel The channel to set.
260         * @param value The value to set.
261         */
262        public void setDIO(final int channel, final boolean value) {
263            int currentDIO = m_fpgaDIO.readDO();
264            if (!value) {
265                currentDIO = (currentDIO & ~(1 << DigitalModule.remapDigitalChannel(channel - 1)));
266            } else {
267                currentDIO = (currentDIO | (1 << DigitalModule.remapDigitalChannel(channel - 1)));
268            }
269            m_fpgaDIO.writeDO(currentDIO);
270        }
271    
272        /**
273         * Read a digital I/O bit from the FPGA.
274         * Get a single value from a digital I/O channel.
275         *
276         * @param channel The channel to read
277         * @return The value of the selected channel
278         */
279        public boolean getDIO(final int channel) {
280            final int currentDIO = m_fpgaDIO.readDI();
281    
282            // Shift 00000001 over channel-1 places.
283            // AND it against the currentDIO
284            // if it == 0, then return 0
285            // else return 1
286            return ((currentDIO >> remapDigitalChannel(channel - 1)) & 1) == 1;
287        }
288    
289        /**
290         * Read the state of all the Digital I/O lines from the FPGA
291         * These are not remapped to logical order.  They are still in hardware order.
292         * @return The state of all the Digital IO lines in hardware order
293         */
294        public short getAllDIO() {
295            return (short) m_fpgaDIO.readDI();
296        }
297    
298        /**
299         * Read the direction of a digital I/O line
300         * @param channel The channel of the DIO to get the direction of.
301         * @return True if the digital channel is configured as an output, false if it is an input
302         */
303        public boolean getDIODirection(int channel) {
304            int currentOutputEnable = m_fpgaDIO.readOutputEnable();
305    
306            //Shift 00000001 over channel-1 places.
307            //AND it against the currentOutputEnable
308            //if it == 0, then return false
309            //else return true
310            return ((currentOutputEnable >> remapDigitalChannel(channel - 1)) & 1) != 0;
311        }
312    
313        /**
314         * Read the direction of all the Digital I/O lines from the FPGA
315         * A 1 bit means output and a 0 bit means input.
316         * These are not remapped to logical order.  They are still in hardware order.
317         * @return The direction of all the Digital IO lines in hardware order
318         */
319        public short getDIODirection() {
320            return (short) m_fpgaDIO.readOutputEnable();
321        }
322    
323        /**
324         * Generate a single pulse.
325         * Write a pulse to the specified digital output channel. There can only be a single pulse going at any time.
326         *
327         * @param channel The channel to pulse.
328         * @param pulseLength The length of the pulse.
329         */
330        public void pulse(final int channel, final int pulseLength) {
331            final short mask = (short) (1 << remapDigitalChannel(channel - 1));
332            m_fpgaDIO.writePulseLength(pulseLength);
333            m_fpgaDIO.writePulse(mask);
334        }
335    
336        /**
337         * Check a DIO line to see if it is currently generating a pulse.
338         *
339         * @param channel The channel to check.
340         * @return True if the channel is pulsing, false otherwise.
341         */
342        public boolean isPulsing(final int channel) {
343            final int mask = 1 << remapDigitalChannel(channel - 1);
344            final int pulseRegister = m_fpgaDIO.readPulse();
345            return (pulseRegister & mask) != 0;
346        }
347    
348        /**
349         * Check if any DIO line is currently generating a pulse.
350         *
351         * @return True if any channel is pulsing, false otherwise.
352         */
353        public boolean isPulsing() {
354            final int pulseRegister = m_fpgaDIO.readPulse();
355            return pulseRegister != 0;
356        }
357    
358        /**
359         * Allocate a DO PWM Generator.
360         * Allocate PWM generators so that they are not accidently reused.
361         */
362        public int allocateDO_PWM() {
363            try {
364                return DO_PWMGenerators[m_moduleNumber - 1].allocate();
365            } catch (CheckedAllocationException e) {
366                throw new AllocationException(
367                    "No Digital Output PWM Generators on module " + m_moduleNumber + " remaining");
368            }
369        }
370    
371        /**
372         * Free the resource associated with a DO PWM generator.
373         */
374        public void freeDO_PWM(int pwmGenerator) {
375            if (pwmGenerator == ~0) return;
376            DO_PWMGenerators[m_moduleNumber - 1].free(pwmGenerator);
377        }
378    
379        /**
380         * Change the frequency of the DO PWM generator.
381         *
382         * The valid range is from 0.6 Hz to 19 kHz.  The frequency resolution is logarithmic.
383         *
384         * @param rate The frequency to output all digital output PWM signals on this module.
385         */
386        public void setDO_PWMRate(double rate) {
387            // Currently rounding in the log rate domain... heavy weight toward picking a higher freq.
388            // TODO: Round in the linear rate domain.
389            byte pwmPeriodPower = (byte)(MathUtils.log(1.0 / (m_fpgaDIO.readLoopTiming() * 0.25E-6 * rate)) / MathUtils.log(2.0) + 0.5);
390            m_fpgaDIO.writeDO_PWMConfig_PeriodPower(pwmPeriodPower);
391        }
392    
393        /**
394         * Configure which DO channel the PWM siganl is output on
395         * @param pwmGenerator The generator index reserved by allocateDO_PWM()
396         * @param channel The Digital Output channel to output on
397         */
398        public void setDO_PWMOutputChannel(int pwmGenerator, int channel) {
399            if (pwmGenerator == ~0) return;
400            switch (pwmGenerator) {
401                case 0:
402                    m_fpgaDIO.writeDO_PWMConfig_OutputSelect_0(remapDigitalChannel(channel - 1));
403                    break;
404                case 1:
405                    m_fpgaDIO.writeDO_PWMConfig_OutputSelect_1(remapDigitalChannel(channel - 1));
406                    break;
407                case 2:
408                    m_fpgaDIO.writeDO_PWMConfig_OutputSelect_2(remapDigitalChannel(channel - 1));
409                    break;
410                case 3:
411                    m_fpgaDIO.writeDO_PWMConfig_OutputSelect_3(remapDigitalChannel(channel - 1));
412                    break;
413            }
414        }
415    
416        /**
417         * Configure the duty-cycle of the PWM generator
418         * @param pwmGenerator The generator index reserved by allocateDO_PWM()
419         * @param dutyCycle The percent duty cycle to output [0..1].
420         */
421        public void setDO_PWMDutyCycle(int pwmGenerator, double dutyCycle) {
422            if (pwmGenerator == ~0) return;
423            if (dutyCycle > 1.0) {
424                dutyCycle = 1.0;
425            }
426            if (dutyCycle < 0.0) {
427                dutyCycle = 0.0;
428            }
429            double rawDutyCycle = 256.0 * dutyCycle;
430            if (rawDutyCycle > 255.5) {
431                rawDutyCycle = 255.5;
432            }
433            byte pwmPeriodPower = m_fpgaDIO.readDO_PWMConfig_PeriodPower();
434            if (pwmPeriodPower < 4) {
435                // The resolution of the duty cycle drops close to the highest frequencies.
436                rawDutyCycle = rawDutyCycle / MathUtils.pow(2.0, 4 - pwmPeriodPower);
437            }
438            m_fpgaDIO.writeDO_PWMDutyCycle(pwmGenerator, (byte)rawDutyCycle);
439        }
440    
441        /**
442         * Return an I2C object for this digital module
443         *
444         * @param address The device address.
445         * @return The associated I2C object.
446         */
447        public I2C getI2C(final int address) {
448            return new I2C(this, address);
449        }
450        
451        /**
452         * Get the loop timing of the Digital Module
453         *
454         * @return The number of clock ticks per DIO loop
455         */
456        public int getLoopTiming() {
457            return tDIO.readLoopTiming();
458        }
459    }