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    }