001/*----------------------------------------------------------------------------*/ 002/* Copyright (c) 2016-2018 FIRST. 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 008package edu.wpi.first.wpilibj; 009 010import java.nio.ByteBuffer; 011 012import edu.wpi.first.wpilibj.hal.FRCNetComm.tResourceType; 013import edu.wpi.first.wpilibj.hal.HAL; 014import edu.wpi.first.wpilibj.hal.SPIJNI; 015 016/** 017 * Represents a SPI bus port. 018 */ 019public class SPI { 020 public enum Port { 021 kOnboardCS0(0), kOnboardCS1(1), kOnboardCS2(2), kOnboardCS3(3), kMXP(4); 022 023 @SuppressWarnings("MemberName") 024 public int value; 025 026 Port(int value) { 027 this.value = value; 028 } 029 } 030 031 private static int devices = 0; 032 033 private int m_port; 034 private int m_bitOrder; 035 private int m_clockPolarity; 036 private int m_dataOnTrailing; 037 038 /** 039 * Constructor. 040 * 041 * @param port the physical SPI port 042 */ 043 public SPI(Port port) { 044 m_port = (byte) port.value; 045 devices++; 046 047 SPIJNI.spiInitialize(m_port); 048 049 HAL.report(tResourceType.kResourceType_SPI, devices); 050 } 051 052 /** 053 * Free the resources used by this object. 054 */ 055 public void free() { 056 if (m_accum != null) { 057 m_accum.free(); 058 m_accum = null; 059 } 060 SPIJNI.spiClose(m_port); 061 } 062 063 /** 064 * Configure the rate of the generated clock signal. The default value is 500,000 Hz. The maximum 065 * value is 4,000,000 Hz. 066 * 067 * @param hz The clock rate in Hertz. 068 */ 069 public final void setClockRate(int hz) { 070 SPIJNI.spiSetSpeed(m_port, hz); 071 } 072 073 /** 074 * Configure the order that bits are sent and received on the wire to be most significant bit 075 * first. 076 */ 077 public final void setMSBFirst() { 078 m_bitOrder = 1; 079 SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity); 080 } 081 082 /** 083 * Configure the order that bits are sent and received on the wire to be least significant bit 084 * first. 085 */ 086 public final void setLSBFirst() { 087 m_bitOrder = 0; 088 SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity); 089 } 090 091 /** 092 * Configure the clock output line to be active low. This is sometimes called clock polarity high 093 * or clock idle high. 094 */ 095 public final void setClockActiveLow() { 096 m_clockPolarity = 1; 097 SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity); 098 } 099 100 /** 101 * Configure the clock output line to be active high. This is sometimes called clock polarity low 102 * or clock idle low. 103 */ 104 public final void setClockActiveHigh() { 105 m_clockPolarity = 0; 106 SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity); 107 } 108 109 /** 110 * Configure that the data is stable on the falling edge and the data changes on the rising edge. 111 */ 112 public final void setSampleDataOnFalling() { 113 m_dataOnTrailing = 1; 114 SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity); 115 } 116 117 /** 118 * Configure that the data is stable on the rising edge and the data changes on the falling edge. 119 */ 120 public final void setSampleDataOnRising() { 121 m_dataOnTrailing = 0; 122 SPIJNI.spiSetOpts(m_port, m_bitOrder, m_dataOnTrailing, m_clockPolarity); 123 } 124 125 /** 126 * Configure the chip select line to be active high. 127 */ 128 public final void setChipSelectActiveHigh() { 129 SPIJNI.spiSetChipSelectActiveHigh(m_port); 130 } 131 132 /** 133 * Configure the chip select line to be active low. 134 */ 135 public final void setChipSelectActiveLow() { 136 SPIJNI.spiSetChipSelectActiveLow(m_port); 137 } 138 139 /** 140 * Write data to the slave device. Blocks until there is space in the output FIFO. 141 * 142 * <p>If not running in output only mode, also saves the data received on the MISO input during 143 * the transfer into the receive FIFO. 144 */ 145 public int write(byte[] dataToSend, int size) { 146 if (dataToSend.length < size) { 147 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 148 } 149 return SPIJNI.spiWriteB(m_port, dataToSend, (byte) size); 150 } 151 152 /** 153 * Write data to the slave device. Blocks until there is space in the output FIFO. 154 * 155 * <p>If not running in output only mode, also saves the data received on the MISO input during 156 * the transfer into the receive FIFO. 157 * 158 * @param dataToSend The buffer containing the data to send. 159 */ 160 public int write(ByteBuffer dataToSend, int size) { 161 if (dataToSend.hasArray()) { 162 return write(dataToSend.array(), size); 163 } 164 if (!dataToSend.isDirect()) { 165 throw new IllegalArgumentException("must be a direct buffer"); 166 } 167 if (dataToSend.capacity() < size) { 168 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 169 } 170 return SPIJNI.spiWrite(m_port, dataToSend, (byte) size); 171 } 172 173 /** 174 * Read a word from the receive FIFO. 175 * 176 * <p>Waits for the current transfer to complete if the receive FIFO is empty. 177 * 178 * <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors. 179 * 180 * @param initiate If true, this function pushes "0" into the transmit buffer and initiates a 181 * transfer. If false, this function assumes that data is already in the receive 182 * FIFO from a previous write. 183 */ 184 public int read(boolean initiate, byte[] dataReceived, int size) { 185 if (dataReceived.length < size) { 186 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 187 } 188 return SPIJNI.spiReadB(m_port, initiate, dataReceived, (byte) size); 189 } 190 191 /** 192 * Read a word from the receive FIFO. 193 * 194 * <p>Waits for the current transfer to complete if the receive FIFO is empty. 195 * 196 * <p>If the receive FIFO is empty, there is no active transfer, and initiate is false, errors. 197 * 198 * @param initiate If true, this function pushes "0" into the transmit buffer and initiates 199 * a transfer. If false, this function assumes that data is already in the 200 * receive FIFO from a previous write. 201 * @param dataReceived The buffer to be filled with the received data. 202 * @param size The length of the transaction, in bytes 203 */ 204 public int read(boolean initiate, ByteBuffer dataReceived, int size) { 205 if (dataReceived.hasArray()) { 206 return read(initiate, dataReceived.array(), size); 207 } 208 if (!dataReceived.isDirect()) { 209 throw new IllegalArgumentException("must be a direct buffer"); 210 } 211 if (dataReceived.capacity() < size) { 212 throw new IllegalArgumentException("buffer is too small, must be at least " + size); 213 } 214 return SPIJNI.spiRead(m_port, initiate, dataReceived, (byte) size); 215 } 216 217 /** 218 * Perform a simultaneous read/write transaction with the device. 219 * 220 * @param dataToSend The data to be written out to the device 221 * @param dataReceived Buffer to receive data from the device 222 * @param size The length of the transaction, in bytes 223 */ 224 public int transaction(byte[] dataToSend, byte[] dataReceived, int size) { 225 if (dataToSend.length < size) { 226 throw new IllegalArgumentException("dataToSend is too small, must be at least " + size); 227 } 228 if (dataReceived.length < size) { 229 throw new IllegalArgumentException("dataReceived is too small, must be at least " + size); 230 } 231 return SPIJNI.spiTransactionB(m_port, dataToSend, dataReceived, (byte) size); 232 } 233 234 /** 235 * Perform a simultaneous read/write transaction with the device. 236 * 237 * @param dataToSend The data to be written out to the device. 238 * @param dataReceived Buffer to receive data from the device. 239 * @param size The length of the transaction, in bytes 240 */ 241 public int transaction(ByteBuffer dataToSend, ByteBuffer dataReceived, int size) { 242 if (dataToSend.hasArray() && dataReceived.hasArray()) { 243 return transaction(dataToSend.array(), dataReceived.array(), size); 244 } 245 if (!dataToSend.isDirect()) { 246 throw new IllegalArgumentException("dataToSend must be a direct buffer"); 247 } 248 if (dataToSend.capacity() < size) { 249 throw new IllegalArgumentException("dataToSend is too small, must be at least " + size); 250 } 251 if (!dataReceived.isDirect()) { 252 throw new IllegalArgumentException("dataReceived must be a direct buffer"); 253 } 254 if (dataReceived.capacity() < size) { 255 throw new IllegalArgumentException("dataReceived is too small, must be at least " + size); 256 } 257 return SPIJNI.spiTransaction(m_port, dataToSend, dataReceived, (byte) size); 258 } 259 260 /** 261 * Initialize automatic SPI transfer engine. 262 * 263 * <p>Only a single engine is available, and use of it blocks use of all other 264 * chip select usage on the same physical SPI port while it is running. 265 * 266 * @param bufferSize buffer size in bytes 267 */ 268 public void initAuto(int bufferSize) { 269 SPIJNI.spiInitAuto(m_port, bufferSize); 270 } 271 272 /** 273 * Frees the automatic SPI transfer engine. 274 */ 275 public void freeAuto() { 276 SPIJNI.spiFreeAuto(m_port); 277 } 278 279 /** 280 * Set the data to be transmitted by the engine. 281 * 282 * <p>Up to 16 bytes are configurable, and may be followed by up to 127 zero 283 * bytes. 284 * 285 * @param dataToSend data to send (maximum 16 bytes) 286 * @param zeroSize number of zeros to send after the data 287 */ 288 public void setAutoTransmitData(byte[] dataToSend, int zeroSize) { 289 SPIJNI.spiSetAutoTransmitData(m_port, dataToSend, zeroSize); 290 } 291 292 /** 293 * Start running the automatic SPI transfer engine at a periodic rate. 294 * 295 * <p>{@link #initAuto(int)} and {@link #setAutoTransmitData(byte[], int)} must 296 * be called before calling this function. 297 * 298 * @param period period between transfers, in seconds (us resolution) 299 */ 300 public void startAutoRate(double period) { 301 SPIJNI.spiStartAutoRate(m_port, period); 302 } 303 304 /** 305 * Start running the automatic SPI transfer engine when a trigger occurs. 306 * 307 * <p>{@link #initAuto(int)} and {@link #setAutoTransmitData(byte[], int)} must 308 * be called before calling this function. 309 * 310 * @param source digital source for the trigger (may be an analog trigger) 311 * @param rising trigger on the rising edge 312 * @param falling trigger on the falling edge 313 */ 314 public void startAutoTrigger(DigitalSource source, boolean rising, boolean falling) { 315 SPIJNI.spiStartAutoTrigger(m_port, source.getPortHandleForRouting(), 316 source.getAnalogTriggerTypeForRouting(), rising, falling); 317 } 318 319 /** 320 * Stop running the automatic SPI transfer engine. 321 */ 322 public void stopAuto() { 323 SPIJNI.spiStopAuto(m_port); 324 } 325 326 /** 327 * Force the engine to make a single transfer. 328 */ 329 public void forceAutoRead() { 330 SPIJNI.spiForceAutoRead(m_port); 331 } 332 333 /** 334 * Read data that has been transferred by the automatic SPI transfer engine. 335 * 336 * <p>Transfers may be made a byte at a time, so it's necessary for the caller 337 * to handle cases where an entire transfer has not been completed. 338 * 339 * <p>Blocks until numToRead bytes have been read or timeout expires. 340 * May be called with numToRead=0 to retrieve how many bytes are available. 341 * 342 * @param buffer buffer where read bytes are stored 343 * @param numToRead number of bytes to read 344 * @param timeout timeout in seconds (ms resolution) 345 * @return Number of bytes remaining to be read 346 */ 347 public int readAutoReceivedData(ByteBuffer buffer, int numToRead, double timeout) { 348 if (buffer.hasArray()) { 349 return readAutoReceivedData(buffer.array(), numToRead, timeout); 350 } 351 if (!buffer.isDirect()) { 352 throw new IllegalArgumentException("must be a direct buffer"); 353 } 354 if (buffer.capacity() < numToRead) { 355 throw new IllegalArgumentException("buffer is too small, must be at least " + numToRead); 356 } 357 return SPIJNI.spiReadAutoReceivedData(m_port, buffer, numToRead, timeout); 358 } 359 360 /** 361 * Read data that has been transferred by the automatic SPI transfer engine. 362 * 363 * <p>Transfers may be made a byte at a time, so it's necessary for the caller 364 * to handle cases where an entire transfer has not been completed. 365 * 366 * <p>Blocks until numToRead bytes have been read or timeout expires. 367 * May be called with numToRead=0 to retrieve how many bytes are available. 368 * 369 * @param buffer array where read bytes are stored 370 * @param numToRead number of bytes to read 371 * @param timeout timeout in seconds (ms resolution) 372 * @return Number of bytes remaining to be read 373 */ 374 public int readAutoReceivedData(byte[] buffer, int numToRead, double timeout) { 375 if (buffer.length < numToRead) { 376 throw new IllegalArgumentException("buffer is too small, must be at least " + numToRead); 377 } 378 return SPIJNI.spiReadAutoReceivedData(m_port, buffer, numToRead, timeout); 379 } 380 381 /** 382 * Get the number of bytes dropped by the automatic SPI transfer engine due 383 * to the receive buffer being full. 384 * 385 * @return Number of bytes dropped 386 */ 387 public int getAutoDroppedCount() { 388 return SPIJNI.spiGetAutoDroppedCount(m_port); 389 } 390 391 private static final int kAccumulateDepth = 2048; 392 393 private static class Accumulator { 394 Accumulator(int port, int xferSize, int validMask, int validValue, int dataShift, 395 int dataSize, boolean isSigned, boolean bigEndian) { 396 m_notifier = new Notifier(this::update); 397 m_buf = ByteBuffer.allocateDirect(xferSize * kAccumulateDepth); 398 m_xferSize = xferSize; 399 m_validMask = validMask; 400 m_validValue = validValue; 401 m_dataShift = dataShift; 402 m_dataMax = 1 << dataSize; 403 m_dataMsbMask = 1 << (dataSize - 1); 404 m_isSigned = isSigned; 405 m_bigEndian = bigEndian; 406 m_port = port; 407 } 408 409 void free() { 410 m_notifier.stop(); 411 } 412 413 final Notifier m_notifier; 414 final ByteBuffer m_buf; 415 final Object m_mutex = new Object(); 416 417 long m_value; 418 int m_count; 419 int m_lastValue; 420 421 int m_center; 422 int m_deadband; 423 424 final int m_validMask; 425 final int m_validValue; 426 final int m_dataMax; // one more than max data value 427 final int m_dataMsbMask; // data field MSB mask (for signed) 428 final int m_dataShift; // data field shift right amount, in bits 429 final int m_xferSize; // SPI transfer size, in bytes 430 final boolean m_isSigned; // is data field signed? 431 final boolean m_bigEndian; // is response big endian? 432 final int m_port; 433 434 void update() { 435 synchronized (m_mutex) { 436 boolean done = false; 437 while (!done) { 438 done = true; 439 440 // get amount of data available 441 int numToRead = SPIJNI.spiReadAutoReceivedData(m_port, m_buf, 0, 0); 442 443 // only get whole responses 444 numToRead -= numToRead % m_xferSize; 445 if (numToRead > m_xferSize * kAccumulateDepth) { 446 numToRead = m_xferSize * kAccumulateDepth; 447 done = false; 448 } 449 if (numToRead == 0) { 450 return; // no samples 451 } 452 453 // read buffered data 454 SPIJNI.spiReadAutoReceivedData(m_port, m_buf, numToRead, 0); 455 456 // loop over all responses 457 for (int off = 0; off < numToRead; off += m_xferSize) { 458 // convert from bytes 459 int resp = 0; 460 if (m_bigEndian) { 461 for (int i = 0; i < m_xferSize; ++i) { 462 resp <<= 8; 463 resp |= ((int) m_buf.get(off + i)) & 0xff; 464 } 465 } else { 466 for (int i = m_xferSize - 1; i >= 0; --i) { 467 resp <<= 8; 468 resp |= ((int) m_buf.get(off + i)) & 0xff; 469 } 470 } 471 472 // process response 473 if ((resp & m_validMask) == m_validValue) { 474 // valid sensor data; extract data field 475 int data = resp >> m_dataShift; 476 data &= m_dataMax - 1; 477 // 2s complement conversion if signed MSB is set 478 if (m_isSigned && (data & m_dataMsbMask) != 0) { 479 data -= m_dataMax; 480 } 481 // center offset 482 data -= m_center; 483 // only accumulate if outside deadband 484 if (data < -m_deadband || data > m_deadband) { 485 m_value += data; 486 } 487 ++m_count; 488 m_lastValue = data; 489 } else { 490 // no data from the sensor; just clear the last value 491 m_lastValue = 0; 492 } 493 } 494 } 495 } 496 } 497 } 498 499 private Accumulator m_accum = null; 500 501 /** 502 * Initialize the accumulator. 503 * 504 * @param period Time between reads 505 * @param cmd SPI command to send to request data 506 * @param xferSize SPI transfer size, in bytes 507 * @param validMask Mask to apply to received data for validity checking 508 * @param validValue After validMask is applied, required matching value for validity checking 509 * @param dataShift Bit shift to apply to received data to get actual data value 510 * @param dataSize Size (in bits) of data field 511 * @param isSigned Is data field signed? 512 * @param bigEndian Is device big endian? 513 */ 514 public void initAccumulator(double period, int cmd, int xferSize, 515 int validMask, int validValue, 516 int dataShift, int dataSize, 517 boolean isSigned, boolean bigEndian) { 518 initAuto(xferSize * 2048); 519 byte[] cmdBytes = new byte[] {0, 0, 0, 0}; 520 if (bigEndian) { 521 for (int i = xferSize - 1; i >= 0; --i) { 522 cmdBytes[i] = (byte) (cmd & 0xff); 523 cmd >>= 8; 524 } 525 } else { 526 cmdBytes[0] = (byte) (cmd & 0xff); 527 cmd >>= 8; 528 cmdBytes[1] = (byte) (cmd & 0xff); 529 cmd >>= 8; 530 cmdBytes[2] = (byte) (cmd & 0xff); 531 cmd >>= 8; 532 cmdBytes[3] = (byte) (cmd & 0xff); 533 } 534 setAutoTransmitData(cmdBytes, xferSize - 4); 535 startAutoRate(period); 536 537 m_accum = new Accumulator(m_port, xferSize, validMask, validValue, dataShift, dataSize, 538 isSigned, bigEndian); 539 m_accum.m_notifier.startPeriodic(period * 1024); 540 } 541 542 /** 543 * Frees the accumulator. 544 */ 545 public void freeAccumulator() { 546 if (m_accum != null) { 547 m_accum.free(); 548 m_accum = null; 549 } 550 freeAuto(); 551 } 552 553 /** 554 * Resets the accumulator to zero. 555 */ 556 public void resetAccumulator() { 557 if (m_accum == null) { 558 return; 559 } 560 synchronized (m_accum.m_mutex) { 561 m_accum.m_value = 0; 562 m_accum.m_count = 0; 563 m_accum.m_lastValue = 0; 564 } 565 } 566 567 /** 568 * Set the center value of the accumulator. 569 * 570 * <p>The center value is subtracted from each value before it is added to the accumulator. This 571 * is used for the center value of devices like gyros and accelerometers to make integration work 572 * and to take the device offset into account when integrating. 573 */ 574 public void setAccumulatorCenter(int center) { 575 if (m_accum == null) { 576 return; 577 } 578 synchronized (m_accum.m_mutex) { 579 m_accum.m_center = center; 580 } 581 } 582 583 /** 584 * Set the accumulator's deadband. 585 */ 586 public void setAccumulatorDeadband(int deadband) { 587 if (m_accum == null) { 588 return; 589 } 590 synchronized (m_accum.m_mutex) { 591 m_accum.m_deadband = deadband; 592 } 593 } 594 595 /** 596 * Read the last value read by the accumulator engine. 597 */ 598 public int getAccumulatorLastValue() { 599 if (m_accum == null) { 600 return 0; 601 } 602 synchronized (m_accum.m_mutex) { 603 m_accum.update(); 604 return m_accum.m_lastValue; 605 } 606 } 607 608 /** 609 * Read the accumulated value. 610 * 611 * @return The 64-bit value accumulated since the last Reset(). 612 */ 613 public long getAccumulatorValue() { 614 if (m_accum == null) { 615 return 0; 616 } 617 synchronized (m_accum.m_mutex) { 618 m_accum.update(); 619 return m_accum.m_value; 620 } 621 } 622 623 /** 624 * Read the number of accumulated values. 625 * 626 * <p>Read the count of the accumulated values since the accumulator was last Reset(). 627 * 628 * @return The number of times samples from the channel were accumulated. 629 */ 630 public int getAccumulatorCount() { 631 if (m_accum == null) { 632 return 0; 633 } 634 synchronized (m_accum.m_mutex) { 635 m_accum.update(); 636 return m_accum.m_count; 637 } 638 } 639 640 /** 641 * Read the average of the accumulated value. 642 * 643 * @return The accumulated average value (value / count). 644 */ 645 public double getAccumulatorAverage() { 646 if (m_accum == null) { 647 return 0; 648 } 649 synchronized (m_accum.m_mutex) { 650 m_accum.update(); 651 if (m_accum.m_count == 0) { 652 return 0.0; 653 } 654 return ((double) m_accum.m_value) / m_accum.m_count; 655 } 656 } 657 658 /** 659 * Read the accumulated value and the number of accumulated values atomically. 660 * 661 * <p>This function reads the value and count atomically. This can be used for averaging. 662 * 663 * @param result AccumulatorResult object to store the results in. 664 */ 665 public void getAccumulatorOutput(AccumulatorResult result) { 666 if (result == null) { 667 throw new IllegalArgumentException("Null parameter `result'"); 668 } 669 if (m_accum == null) { 670 result.value = 0; 671 result.count = 0; 672 return; 673 } 674 synchronized (m_accum.m_mutex) { 675 m_accum.update(); 676 result.value = m_accum.m_value; 677 result.count = m_accum.m_count; 678 } 679 } 680}