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 }