text: /*----------------------------------------------------------------------------*/
  /* Copyright (c) 2008-2019 FIRST. All Rights Reserved.                        */
  /* Open Source Software - may be modified and shared by FRC teams. The code   */
  /* must be accompanied by the FIRST BSD license file in the root directory of */
  /* the project.                                                               */
  /*----------------------------------------------------------------------------*/
  
  #pragma once
  
  #include <stdint.h>
  
  #include <memory>
  
  #include <hal/SPITypes.h>
  #include <wpi/ArrayRef.h>
  #include <wpi/deprecated.h>
  
  #include "frc/ErrorBase.h"
  
  namespace frc {
  
  class DigitalSource;
  
  /**
   * SPI bus interface class.
   *
   * This class is intended to be used by sensor (and other SPI device) drivers.
   * It probably should not be used directly.
   *
   */
  class SPI : public ErrorBase {
   public:
    enum Port { kOnboardCS0 = 0, kOnboardCS1, kOnboardCS2, kOnboardCS3, kMXP };
  
    /**
     * Constructor
     *
     * @param port the physical SPI port
     */
    explicit SPI(Port port);
  
    ~SPI() override;
  
    SPI(SPI&& rhs);
    SPI& operator=(SPI&& rhs);
  
    /**
     * Configure the rate of the generated clock signal.
     *
     * The default value is 500,000Hz.
     * The maximum value is 4,000,000Hz.
     *
     * @param hz The clock rate in Hertz.
     */
    void SetClockRate(int hz);
  
    /**
     * Configure the order that bits are sent and received on the wire
     * to be most significant bit first.
     */
    void SetMSBFirst();
  
    /**
     * Configure the order that bits are sent and received on the wire
     * to be least significant bit first.
     */
    void SetLSBFirst();
  
    /**
     * Configure that the data is stable on the leading edge and the data
     * changes on the trailing edge.
     */
    void SetSampleDataOnLeadingEdge();
  
    /**
     * Configure that the data is stable on the trailing edge and the data
     * changes on the leading edge.
     */
    void SetSampleDataOnTrailingEdge();
  
    /**
     * Configure that the data is stable on the falling edge and the data
     * changes on the rising edge.
     */
    WPI_DEPRECATED("Use SetSampleDataOnTrailingEdge in most cases.")
    void SetSampleDataOnFalling();
  
    /**
     * Configure that the data is stable on the rising edge and the data
     * changes on the falling edge.
     */
    WPI_DEPRECATED("Use SetSampleDataOnLeadingEdge in most cases")
    void SetSampleDataOnRising();
  
    /**
     * Configure the clock output line to be active low.
     * This is sometimes called clock polarity high or clock idle high.
     */
    void SetClockActiveLow();
  
    /**
     * Configure the clock output line to be active high.
     * This is sometimes called clock polarity low or clock idle low.
     */
    void SetClockActiveHigh();
  
    /**
     * Configure the chip select line to be active high.
     */
    void SetChipSelectActiveHigh();
  
    /**
     * Configure the chip select line to be active low.
     */
    void SetChipSelectActiveLow();
  
    /**
     * Write data to the slave device.  Blocks until there is space in the
     * output FIFO.
     *
     * If not running in output only mode, also saves the data received
     * on the MISO input during the transfer into the receive FIFO.
     */
    virtual int Write(uint8_t* data, int size);
  
    /**
     * Read a word from the receive FIFO.
     *
     * Waits for the current transfer to complete if the receive FIFO is empty.
     *
     * If the receive FIFO is empty, there is no active transfer, and initiate
     * is false, errors.
     *
     * @param initiate If true, this function pushes "0" into the transmit buffer
     *                 and initiates a transfer. If false, this function assumes
     *                 that data is already in the receive FIFO from a previous
     *                 write.
     */
    virtual int Read(bool initiate, uint8_t* dataReceived, int size);
  
    /**
     * Perform a simultaneous read/write transaction with the device
     *
     * @param dataToSend   The data to be written out to the device
     * @param dataReceived Buffer to receive data from the device
     * @param size         The length of the transaction, in bytes
     */
    virtual int Transaction(uint8_t* dataToSend, uint8_t* dataReceived, int size);
  
    /**
     * Initialize automatic SPI transfer engine.
     *
     * Only a single engine is available, and use of it blocks use of all other
     * chip select usage on the same physical SPI port while it is running.
     *
     * @param bufferSize buffer size in bytes
     */
    void InitAuto(int bufferSize);
  
    /**
     * Frees the automatic SPI transfer engine.
     */
    void FreeAuto();
  
    /**
     * Set the data to be transmitted by the engine.
     *
     * Up to 16 bytes are configurable, and may be followed by up to 127 zero
     * bytes.
     *
     * @param dataToSend data to send (maximum 16 bytes)
     * @param zeroSize number of zeros to send after the data
     */
    void SetAutoTransmitData(wpi::ArrayRef<uint8_t> dataToSend, int zeroSize);
  
    /**
     * Start running the automatic SPI transfer engine at a periodic rate.
     *
     * InitAuto() and SetAutoTransmitData() must be called before calling this
     * function.
     *
     * @param period period between transfers, in seconds (us resolution)
     */
    void StartAutoRate(double period);
  
    /**
     * Start running the automatic SPI transfer engine when a trigger occurs.
     *
     * InitAuto() and SetAutoTransmitData() must be called before calling this
     * function.
     *
     * @param source digital source for the trigger (may be an analog trigger)
     * @param rising trigger on the rising edge
     * @param falling trigger on the falling edge
     */
    void StartAutoTrigger(DigitalSource& source, bool rising, bool falling);
  
    /**
     * Stop running the automatic SPI transfer engine.
     */
    void StopAuto();
  
    /**
     * Force the engine to make a single transfer.
     */
    void ForceAutoRead();
  
    /**
     * Read data that has been transferred by the automatic SPI transfer engine.
     *
     * Transfers may be made a byte at a time, so it's necessary for the caller
     * to handle cases where an entire transfer has not been completed.
     *
     * Each received data sequence consists of a timestamp followed by the
     * received data bytes, one byte per word (in the least significant byte).
     * The length of each received data sequence is the same as the combined
     * size of the data and zeroSize set in SetAutoTransmitData().
     *
     * Blocks until numToRead words have been read or timeout expires.
     * May be called with numToRead=0 to retrieve how many words are available.
     *
     * @param buffer buffer where read words are stored
     * @param numToRead number of words to read
     * @param timeout timeout in seconds (ms resolution)
     * @return Number of words remaining to be read
     */
    int ReadAutoReceivedData(uint32_t* buffer, int numToRead, double timeout);
  
    /**
     * Get the number of bytes dropped by the automatic SPI transfer engine due
     * to the receive buffer being full.
     *
     * @return Number of bytes dropped
     */
    int GetAutoDroppedCount();
  
    /**
     * Initialize the accumulator.
     *
     * @param period    Time between reads
     * @param cmd       SPI command to send to request data
     * @param xferSize  SPI transfer size, in bytes
     * @param validMask Mask to apply to received data for validity checking
     * @param validData After valid_mask is applied, required matching value for
     *                  validity checking
     * @param dataShift Bit shift to apply to received data to get actual data
     *                  value
     * @param dataSize  Size (in bits) of data field
     * @param isSigned  Is data field signed?
     * @param bigEndian Is device big endian?
     */
    void InitAccumulator(double period, int cmd, int xferSize, int validMask,
                         int validValue, int dataShift, int dataSize,
                         bool isSigned, bool bigEndian);
  
    /**
     * Frees the accumulator.
     */
    void FreeAccumulator();
  
    /**
     * Resets the accumulator to zero.
     */
    void ResetAccumulator();
  
    /**
     * Set the center value of the accumulator.
     *
     * The center value is subtracted from each value before it is added to the
     * accumulator. This is used for the center value of devices like gyros and
     * accelerometers to make integration work and to take the device offset into
     * account when integrating.
     */
    void SetAccumulatorCenter(int center);
  
    /**
     * Set the accumulator's deadband.
     */
    void SetAccumulatorDeadband(int deadband);
  
    /**
     * Read the last value read by the accumulator engine.
     */
    int GetAccumulatorLastValue() const;
  
    /**
     * Read the accumulated value.
     *
     * @return The 64-bit value accumulated since the last Reset().
     */
    int64_t GetAccumulatorValue() const;
  
    /**
     * Read the number of accumulated values.
     *
     * Read the count of the accumulated values since the accumulator was last
     * Reset().
     *
     * @return The number of times samples from the channel were accumulated.
     */
    int64_t GetAccumulatorCount() const;
  
    /**
     * Read the average of the accumulated value.
     *
     * @return The accumulated average value (value / count).
     */
    double GetAccumulatorAverage() const;
  
    /**
     * Read the accumulated value and the number of accumulated values atomically.
     *
     * This function reads the value and count atomically.
     * This can be used for averaging.
     *
     * @param value Pointer to the 64-bit accumulated output.
     * @param count Pointer to the number of accumulation cycles.
     */
    void GetAccumulatorOutput(int64_t& value, int64_t& count) const;
  
    /**
     * Set the center value of the accumulator integrator.
     *
     * The center value is subtracted from each value*dt before it is added to the
     * integrated value. This is used for the center value of devices like gyros
     * and accelerometers to take the device offset into account when integrating.
     */
    void SetAccumulatorIntegratedCenter(double center);
  
    /**
     * Read the integrated value.  This is the sum of (each value * time between
     * values).
     *
     * @return The integrated value accumulated since the last Reset().
     */
    double GetAccumulatorIntegratedValue() const;
  
    /**
     * Read the average of the integrated value.  This is the sum of (each value
     * times the time between values), divided by the count.
     *
     * @return The average of the integrated value accumulated since the last
     *         Reset().
     */
    double GetAccumulatorIntegratedAverage() const;
  
   protected:
    HAL_SPIPort m_port = HAL_SPI_kInvalid;
    bool m_msbFirst = false;          // Default little-endian
    bool m_sampleOnTrailing = false;  // Default data updated on falling edge
    bool m_clockIdleHigh = false;     // Default clock active high
  
   private:
    void Init();
  
    class Accumulator;
    std::unique_ptr<Accumulator> m_accum;
  };
  
  }  // namespace frc
  