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 }