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 }