001 //Inspired by http://www.chiefdelphi.com/forums/showthread.php?t=97885&highlight=SPI 002 package edu.wpi.first.wpilibj; 003 004 import edu.wpi.first.wpilibj.communication.UsageReporting; 005 import edu.wpi.first.wpilibj.fpga.tDIO; 006 import edu.wpi.first.wpilibj.fpga.tSPI; 007 008 /** 009 * 010 * Represents a device on an SPI bus 011 * Note that the cRIO only supports one SPI bus 012 * Attempting to open a second SPI device with a 013 * different shared pin (clk, mosi, miso) will 014 * result in an exception 015 * 016 * @author mwils 017 */ 018 public class SPIDevice extends SensorBase { 019 020 //used to synchronize access to the SPI bus 021 private static final Object semaphore = new Object(); 022 //tSPI instance 023 private static tSPI spi = null; 024 private static boolean createdBusChannels = false; 025 private static DigitalOutput clkChannel = null; 026 private static DigitalOutput mosiChannel = null; 027 private static DigitalInput misoChannel = null; 028 private static int devices = 0; 029 030 /** 031 * Initialize SPI bus<br> 032 * Only call this method once in the program 033 * 034 * @param clk The digital output for the clock signal. 035 * @param mosi The digital output for the written data to the slave 036 * (master-out slave-in). 037 * @param miso The digital input for the input data from the slave 038 * (master-in slave-out). 039 */ 040 private static void initBus(final DigitalOutput clk, final DigitalOutput mosi, final DigitalInput miso) { 041 if (spi == null) 042 spi = new tSPI(); 043 else if(clk.getChannel() == clkChannel.getChannel() && mosi.getChannel() == mosiChannel.getChannel() 044 && miso.getChannel() == misoChannel.getChannel()) 045 return; 046 else 047 throw new BadSPIConfigException("SPI bus already configured with different clk, mosi and/or miso"); 048 049 clkChannel = clk; 050 mosiChannel = mosi; 051 misoChannel = miso; 052 053 054 tSPI.writeChannels_SCLK_Module(clk.getModuleForRouting()); 055 tSPI.writeChannels_SCLK_Channel(clk.getChannelForRouting()); 056 057 058 if (mosi != null) { 059 tSPI.writeChannels_MOSI_Module(mosi.getModuleForRouting()); 060 tSPI.writeChannels_MOSI_Channel(mosi.getChannelForRouting()); 061 } else { 062 tSPI.writeChannels_MOSI_Module(0); 063 tSPI.writeChannels_MOSI_Channel(0); 064 } 065 066 if (miso != null) { 067 tSPI.writeChannels_MISO_Module(miso.getModuleForRouting()); 068 tSPI.writeChannels_MISO_Channel(miso.getChannelForRouting()); 069 tSPI.writeConfig_WriteOnly(false);//TODO check these are right 070 } else { 071 tSPI.writeChannels_MISO_Module(0); 072 tSPI.writeChannels_MISO_Channel(0); 073 tSPI.writeConfig_WriteOnly(true); 074 } 075 076 077 tSPI.writeChannels_SS_Module(0); 078 tSPI.writeChannels_SS_Channel(0); 079 080 tSPI.strobeReset(); 081 tSPI.strobeClearReceivedData(); 082 } 083 084 /** 085 * Cleanup the SPI Bus 086 */ 087 private static void freeBus(boolean freeChannels) { 088 if (spi != null) { 089 spi.Release(); 090 spi = null; 091 } 092 if(freeChannels){ 093 clkChannel.free(); 094 mosiChannel.free(); 095 misoChannel.free(); 096 } 097 } 098 099 /** 100 * Perform a SPI transfer with the length of the selected device's current 101 * configuration 102 * 103 * @param value The value to write to the bus 104 */ 105 private static long trasferStatic(long value) { 106 synchronized (semaphore) { 107 tSPI.strobeClearReceivedData(); 108 109 tSPI.writeDataToLoad(value); 110 tSPI.strobeLoad(); 111 if (!tSPI.readConfig_WriteOnly()) 112 { 113 while (tSPI.readReceivedElements() == 0) { 114 Thread.yield(); 115 116 } 117 118 tSPI.strobeReadReceivedData(); 119 return tSPI.readReceivedData(); 120 } 121 else 122 return(0); 123 } 124 } 125 126 /** 127 * When transferring data the it is sent and received with the most 128 * significant bit first 129 * 130 * @see #setBitOrder(boolean) 131 */ 132 public static final boolean BIT_ORDER_MSB_FIRST = true; 133 /** 134 * When transferring data the it is sent and received with the least 135 * significant bit first 136 * 137 * @see #setBitOrder(boolean) 138 */ 139 public static final boolean BIT_ORDER_LSB_FIRST = false; 140 /** 141 * When transferring data the clock will be active high<br> This corresponds 142 * to CPOL=0 143 * 144 * @see #setClockPolarity(boolean) 145 */ 146 public static final boolean CLOCK_POLARITY_ACTIVE_HIGH = false; 147 /** 148 * When transferring data the clock will be active low<br> This corresponds 149 * to CPOL=1 150 * 151 * @see #setClockPolarity(boolean) 152 */ 153 public static final boolean CLOCK_POLARITY_ACTIVE_LOW = true; 154 /** 155 * Data is valid on the leading edge of the clock pulse<br> This corresponds 156 * to CPHA=0 157 * 158 * @see #setDataOnFalling(boolean) 159 */ 160 public static final boolean DATA_ON_LEADING_EDGE = false; 161 /** 162 * Data is valid on the trailing edge of the clock pulse<br> This 163 * corresponds to CPHA=1 164 * 165 * @see #setDataOnFalling(boolean) 166 */ 167 public static final boolean DATA_ON_TRAILING_EDGE = true; 168 /** 169 * The CS will be brought high when the device is selected 170 */ 171 public static final boolean CS_ACTIVE_HIGH = true; 172 /** 173 * The CS will be brought low when the device is selected 174 */ 175 public static final boolean CS_ACTIVE_LOW = false; 176 /** 177 * The chip select line is active for the duration of the frame. 178 * 179 * @see #setFrameMode(char) 180 */ 181 public static final char FRAME_MODE_CHIP_SELECT = 0; 182 /** 183 * The chip select line pulses before the transfer of each frame. 184 * 185 * @see #setFrameMode(char) 186 */ 187 public static final char FRAME_MODE_PRE_LATCH = 1; 188 /** 189 * The chip select line pulses after the transfer of each frame. 190 * 191 * @see #setFrameMode(char) 192 */ 193 public static final char FRAME_MODE_POST_LATCH = 2; 194 /** 195 * The chip select line pulses before and after each frame. 196 * 197 * @see #setFrameMode(char) 198 */ 199 public static final char FRAME_MODE_PRE_POST_LATCH_PULSE = 3; 200 201 private boolean createdCSChannel = false; 202 private DigitalOutput cs = null; 203 204 private boolean csActiveHigh; 205 private boolean bitOrder = BIT_ORDER_MSB_FIRST; 206 private boolean clockPolarity = CLOCK_POLARITY_ACTIVE_HIGH; 207 private boolean dataOnTrailing = DATA_ON_LEADING_EDGE; 208 private int clockHalfPeriodDelay = 0;//fastest clockrate possible 209 private char frameMode = FRAME_MODE_CHIP_SELECT; 210 /** 211 * The maximum rate the clock can transmit at 212 */ 213 public final double MAX_CLOCK_FREQUENCY = 1/(2*tDIO.readLoopTiming()/(kSystemClockTicksPerMicrosecond*1e6)); 214 /** 215 * The minimum rate the clock can transmit at 216 */ 217 public final double MIN_CLOCK_FREQUENCY = 1/255*(2*tDIO.readLoopTiming()/(kSystemClockTicksPerMicrosecond*1e6)); 218 219 /** 220 * Create a new device on the SPI bus. 221 * The chip select line is active low 222 * 223 * @param slot The module of the digital IO for the device 224 * @param clkChannel The channel number for the clk channel 225 * @param mosiChannel The channel number for the mosi (output) channel 226 * @param misoChannel The channel number for the miso (input) channel 227 * @param csChannel The channel number for the chip select channel 228 */ 229 public SPIDevice(int slot, int clkChannel, int mosiChannel, int misoChannel, int csChannel) { 230 this(new DigitalOutput(slot, clkChannel), new DigitalOutput (slot, mosiChannel), 231 new DigitalInput(slot, misoChannel), new DigitalOutput(slot, csChannel), CS_ACTIVE_LOW, true); 232 } 233 234 /** 235 * Create a new device on the SPI bus. 236 * 237 * @param slot The module of the digital IO for the device 238 * @param clkChannel The channel number for the clk channel 239 * @param mosiChannel The channel number for the mosi (output) channel 240 * @param misoChannel The channel number for the miso (input) channel 241 * @param csChannel The channel number for the chip select channel 242 * @param csActiveHigh True if the chip select line should be high when 243 * the device is selected. False if it should be low. 244 */ 245 public SPIDevice(int slot, int clkChannel, int mosiChannel, int misoChannel, int csChannel, boolean csActiveHigh) { 246 this(new DigitalOutput(slot, clkChannel), new DigitalOutput (slot, mosiChannel), 247 new DigitalInput(slot, misoChannel), new DigitalOutput(slot, csChannel), csActiveHigh, true); 248 } 249 250 /** 251 * Create a new device on the SPI bus. 252 * The chip select line is active low 253 * 254 * @param clk The clock channel 255 * @param mosi The mosi (output) channel 256 * @param miso The miso (input) channel 257 * @param cs The chip select channel 258 */ 259 public SPIDevice(DigitalOutput clk, DigitalOutput mosi, DigitalInput miso, DigitalOutput cs) { 260 this(clk, mosi, miso, cs, CS_ACTIVE_LOW, false); 261 } 262 263 /** 264 * Create a new device on the SPI bus. 265 * 266 * @param clk The clock channel 267 * @param mosi The mosi (output) channel 268 * @param miso The miso (input) channel 269 * @param cs The chip select channel 270 * @param csActiveHigh True if the chip select line should be high when 271 * the device is selected. False if it should be low. 272 */ 273 public SPIDevice(DigitalOutput clk, DigitalOutput mosi, DigitalInput miso, DigitalOutput cs, boolean csActiveHigh) { 274 this(clk, mosi, miso, cs, csActiveHigh, false); 275 } 276 277 /** 278 * Create a new device on the SPI bus. 279 * Must only be used after a device has been created establishing the 280 * clk, mosi and miso channels. The chip select line is active low 281 * 282 * @param slot The module of the digital output for the device's chip select pin 283 * @param csChannel The channel for the digital output for the device's chip select pin 284 */ 285 public SPIDevice(int slot, int csChannel) { 286 this(new DigitalOutput(slot, csChannel), CS_ACTIVE_LOW, true); 287 } 288 289 /** 290 * Create a new device on the SPI bus. 291 * Must only be used after a device has been created establishing the 292 * clk, mosi and miso channels. 293 * 294 * @param slot The module of the digital output for the device's chip select pin 295 * @param csChannel The channel for the digital output for the device's chip select pin 296 * @param csActiveHigh True if the chip select line should be high when 297 * the device is selected. False if it should be low. 298 */ 299 public SPIDevice(int slot, int csChannel, boolean csActiveHigh) { 300 this(new DigitalOutput(slot, csChannel), csActiveHigh, true); 301 } 302 303 /** 304 * Create a new device on the SPI bus. 305 * Must only be used after a device has been created establishing the 306 * clk, mosi and miso channels. The chip select line is active low 307 * 308 * @param cs The chip select channel 309 */ 310 public SPIDevice(DigitalOutput cs) { 311 this(cs, CS_ACTIVE_LOW); 312 } 313 314 /** 315 * Create a new device on the SPI bus. 316 * Must only be used after a device has been created establishing the 317 * clk, mosi and miso channels. 318 * 319 * @param cs The chip select channel 320 * @param csActiveHigh True if the chip select line should be high when 321 * the device is selected. False if it should be low. 322 */ 323 public SPIDevice(DigitalOutput cs, boolean csActiveHigh) { 324 this(cs, csActiveHigh, false); 325 } 326 327 /** 328 * Create a new device on the SPI bus. 329 * 330 * @param clk The clock channel 331 * @param mosi The mosi (output) channel 332 * @param miso The miso (input) channel 333 * @param cs The chip select channel 334 * @param csActiveHigh True if the chip select line should be high when 335 * the device is selected. False if it should be low. 336 * @param createdChannel True if this class owns the DigitalInput/Output objects 337 */ 338 private SPIDevice(DigitalOutput clk, DigitalOutput mosi, DigitalInput miso, 339 DigitalOutput cs, boolean csActiveHigh, boolean createdChannel) { 340 initBus(clk, mosi, miso); 341 devices += 1; 342 this.createdBusChannels = createdChannel; 343 this.createdCSChannel = createdChannel; 344 this.cs = cs; 345 this.csActiveHigh = csActiveHigh; 346 cs.set(!csActiveHigh); 347 348 UsageReporting.report(UsageReporting.kResourceType_SPI, devices); 349 } 350 351 /** 352 * Create a new device on the SPI bus. 353 * Must only be used after a device has been created establishing the 354 * clk, mosi and miso channels. 355 * 356 * @param slot The module of the digital output for the device's chip select pin 357 * @param csChannel The channel for the digital output for the device's chip select pin 358 * @param csActiveHigh True if the chip select line should be high when 359 * the device is selected. False if it should be low. 360 * @param createdChannel True if this class owns the DigitalInput/Output objects 361 */ 362 private SPIDevice(DigitalOutput cs, boolean csActiveHigh, boolean createdChannel) { 363 if (spi == null) { 364 throw new RuntimeException("Must create SPI with clk, miso and mosi first"); 365 } 366 devices += 1; 367 this.createdCSChannel = createdChannel; 368 this.cs = cs; 369 this.csActiveHigh = csActiveHigh; 370 cs.set(!csActiveHigh); 371 372 UsageReporting.report(UsageReporting.kResourceType_SPI, devices); 373 } 374 375 /** 376 * Free the resources used by this object 377 */ 378 public void free(){ 379 if(createdCSChannel && cs!=null) 380 cs.free(); 381 devices -= 1; 382 if (devices == 0) 383 freeBus(createdBusChannels); 384 } 385 386 /** 387 * Perform a SPI transfer with the length of this device's current 388 * configuration. This will select the device, transfer the data and then 389 * deselect the device 390 * 391 * @param writeValue The value to write to the device 392 * @param numBits The number of bits to write/read 393 */ 394 public long transfer(long writeValue, int numBits) { 395 long[] readValue; 396 synchronized (semaphore) { 397 readValue = transfer(new long[]{writeValue}, new int[]{numBits}); 398 } 399 return readValue[0]; 400 } 401 402 /** 403 * Perform a SPI transfer where an array of bits are written and read. The 404 * number of bits to write and read is specified in numBits<br> The whole 405 * transfer will occur with the cs line held active throughout 406 * 407 * @param writeValues The value to write to the device 408 * @param numBits The number of bits to write/read 409 */ 410 public long[] transfer(long[] writeValues, int[] numBits) { 411 if (writeValues.length != numBits.length) { 412 throw new BadSPIConfigException("The number of values to write does not match array of data lengths"); 413 } 414 for (int i = 0; i < numBits.length; ++i) { 415 if (numBits[i] < 1 || numBits[i] > 32) { 416 throw new BadSPIConfigException("All values in the data length must be >0 and <=32"); 417 } 418 } 419 long[] readValues = new long[writeValues.length]; 420 synchronized (semaphore) { 421 tSPI.writeConfig_MSBfirst(bitOrder); 422 tSPI.writeConfig_ClockHalfPeriodDelay(clockHalfPeriodDelay); 423 tSPI.writeConfig_ClockPolarity(clockPolarity); 424 tSPI.writeConfig_DataOnFalling(dataOnTrailing); 425 426 tSPI.writeConfig_FramePolarity(!csActiveHigh); 427 //Set up FPGA for chip select 428 writeFrameMode(frameMode); 429 tSPI.writeChannels_SS_Module(cs.getModuleForRouting()); 430 tSPI.writeChannels_SS_Channel(cs.getChannelForRouting()); 431 432 for (int i = 0; i < writeValues.length; ++i) { 433 tSPI.writeConfig_BusBitWidth(numBits[i]); 434 readValues[i] = trasferStatic(writeValues[i]); 435 } 436 } 437 return readValues; 438 } 439 440 /** 441 * Sets the bit order of the transfer sent and received values. 442 * The value transfered/received will always be the lowest bits 443 * of the value. This method just sets the order in which those 444 * bits are transfered. 445 * 446 * @param bitOrder true=Most significant bit first, false=Least significant 447 * bit first 448 * 449 * @see #BIT_ORDER_MSB_FIRST 450 * @see #BIT_ORDER_LSB_FIRST 451 */ 452 public final void setBitOrder(boolean bitOrder) { 453 this.bitOrder = bitOrder; 454 } 455 456 /** 457 * Sets the polarity of the clock when transferring data to the device 458 * 459 * @param clockPolarity true=Clock active low, false=Clock active high 460 * 461 * @see #CLOCK_POLARITY_ACTIVE_HIGH 462 * @see #CLOCK_POLARITY_ACTIVE_LOW 463 */ 464 public final void setClockPolarity(boolean clockPolarity) { 465 this.clockPolarity = clockPolarity; 466 } 467 468 /** 469 * If Data is valid at the beginning of the clock pulse or the end of the 470 * clock pulse 471 * 472 * @param dataOnTrailing true=Process data on the trailing edge of the clock, 473 * false=Process data on leading edge of the clock 474 * 475 * @see #DATA_ON_LEADING_EDGE 476 * @see #DATA_ON_TRAILING_EDGE 477 */ 478 public final void setDataOnTrailing(boolean dataOnTrailing) { 479 this.dataOnTrailing = dataOnTrailing; 480 } 481 482 /** 483 * Sets the Frame Mode which specifies the behavior of the chip select line in relation to 484 * the duration of the frame. 485 * 486 * @param frameMode 0 = low for duration of frame, 1 = pulse before transfer, 487 * 2 = pulse after transfer, 3 = pulse before and after transfer 488 * 489 * @see #FRAME_MODE_CHIP_SELECT 490 * @see #FRAME_MODE_PRE_LATCH 491 * @see #FRAME_MODE_POST_LATCH 492 * @see #FRAME_MODE_PRE_POST_LATCH_PULSE 493 */ 494 public final void setFrameMode(char frameMode) 495 { 496 this.frameMode = frameMode; 497 } 498 499 /** 500 * Set the frequence of the clock when sending data 501 * 502 * @param hz The frequency of the clock in hz 503 * 504 * @see #MIN_CLOCK_FREQUENCY 505 * @see #MAX_CLOCK_FREQUENCY 506 */ 507 public final void setClockRate(double hz) { 508 509 double v = (1.0 / hz) / (2 * tDIO.readLoopTiming() / (kSystemClockTicksPerMicrosecond * 1e6)); 510 if (v < 1) { 511 throw new BadSPIConfigException("Clock Rate too high. Hz: " + hz); 512 } 513 int delay = (int) (v + .5); 514 if (delay > 255) { 515 throw new BadSPIConfigException("Clock Rate too low. Hz: " + hz); 516 } 517 518 clockHalfPeriodDelay = delay; 519 } 520 521 /** 522 * Set the frame mode of the SPI bus 523 * 524 * @param frameMode The frame mode to set 525 * 526 * @see #setFrameMode(char) 527 */ 528 private void writeFrameMode(char frameMode) 529 { 530 switch (frameMode) 531 { 532 case 0: default: 533 tSPI.writeConfig_LatchFirst(false); 534 tSPI.writeConfig_LatchLast(false); 535 break; 536 case 1: 537 tSPI.writeConfig_LatchFirst(true); 538 tSPI.writeConfig_LatchLast(false); 539 break; 540 case 2: 541 tSPI.writeConfig_LatchFirst(false); 542 tSPI.writeConfig_LatchLast(true); 543 break; 544 case 3: 545 tSPI.writeConfig_LatchFirst(true); 546 tSPI.writeConfig_LatchLast(true); 547 break; 548 } 549 } 550 public static class BadSPIConfigException extends RuntimeException { 551 552 public BadSPIConfigException(String message) { 553 super(message); 554 } 555 } 556 }