Add code for talking to the Teensy's SPI peripheral
Change-Id: I3ddaa616e3f84fa74a2e7ab68726d5cb5cdbc9de
diff --git a/motors/peripheral/BUILD b/motors/peripheral/BUILD
index 2c71973..c8b63e9 100644
--- a/motors/peripheral/BUILD
+++ b/motors/peripheral/BUILD
@@ -90,3 +90,17 @@
"//motors/core",
],
)
+
+cc_library(
+ name = "spi",
+ srcs = ["spi.cc"],
+ hdrs = ["spi.h"],
+ restricted_to = mcu_cpus,
+ visibility = ["//visibility:public"],
+ deps = [
+ ":uart_buffer",
+ "//motors:util",
+ "//motors/core",
+ "//third_party/GSL",
+ ],
+)
diff --git a/motors/peripheral/spi.cc b/motors/peripheral/spi.cc
new file mode 100644
index 0000000..c0bb767
--- /dev/null
+++ b/motors/peripheral/spi.cc
@@ -0,0 +1,140 @@
+#include "motors/peripheral/spi.h"
+
+#include <stdint.h>
+
+#include "motors/core/time.h"
+
+namespace frc971 {
+namespace teensy {
+
+Spi::~Spi() {
+ DisableTransmitInterrupt();
+ DisableReceiveInterrupt();
+ FlushInterruptRequests();
+}
+
+void Spi::Initialize() {
+ // Use all the defaults (including slave mode).
+ mcr_value_ =
+ V_SPI_PCSIS(1) /* CS0 is active low. It appears to ignore this though? */;
+ module_->MCR = mcr_value_;
+ module_->CTAR[0] = V_SPI_FMSZ(7) /* 8 bit frames */ |
+ M_SPI_CPOL /* Idle high clock */ |
+ M_SPI_CPHA /* Data changed on leading clock edge */;
+}
+
+void Spi::ClearQueues() {
+ module_->MCR = mcr_value_ | M_SPI_HALT;
+ const bool receive_overflow = module_->SR & M_SPI_RFOF;
+ module_->MCR = mcr_value_ | M_SPI_CLR_TXF | M_SPI_CLR_RXF | M_SPI_DIS_TXF |
+ M_SPI_DIS_RXF | M_SPI_HALT;
+ if (receive_overflow) {
+ (void)module_->POPR;
+ }
+ module_->SR = M_SPI_TFUF | M_SPI_TFFF | M_SPI_RFOF | M_SPI_RFDF;
+ module_->MCR = mcr_value_;
+}
+
+InterruptBufferedSpi::~InterruptBufferedSpi() {
+ spi_.DisableReceiveInterrupt();
+ spi_.FlushInterruptRequests();
+}
+
+void InterruptBufferedSpi::Initialize() {
+ spi_.Initialize();
+ transmit_buffer_.clear();
+ receive_buffer_.clear();
+ frames_to_receive_ = 0;
+}
+
+void InterruptBufferedSpi::ClearQueues(const DisableInterrupts &) {
+ spi_.ClearQueues();
+ transmit_buffer_.clear();
+ receive_buffer_.clear();
+ frames_to_receive_ = 0;
+ spi_.DisableTransmitInterrupt();
+ spi_.DisableReceiveInterrupt();
+ spi_.FlushInterruptRequests();
+}
+
+void InterruptBufferedSpi::Write(gsl::span<const char> data,
+ DisableInterrupts *disable_interrupts) {
+ frames_to_receive_ += data.size();
+ // Until we get all of the data queued, we'll call WriteFrames from our
+ // context, so don't enable the interrupt yet.
+ while (!data.empty()) {
+ const int bytes_written = transmit_buffer_.PushSpan(data);
+ data = data.subspan(bytes_written);
+ while (ReadAndWriteFrame(data.empty(), *disable_interrupts)) {
+ }
+ ReenableInterrupts{disable_interrupts};
+ }
+ // If there's still data queued, then we need to enable the interrupt so it
+ // can push it to the hardware.
+ if (!transmit_buffer_.empty()) {
+ spi_.EnableTransmitInterrupt();
+ }
+ if (frames_to_receive_ > 0) {
+ spi_.EnableReceiveInterrupt();
+ }
+ if (!transmit_buffer_.empty() || frames_to_receive_ > 0) {
+ spi_.FlushInterruptRequests();
+ }
+}
+
+gsl::span<char> InterruptBufferedSpi::Read(
+ gsl::span<char> buffer, DisableInterrupts *disable_interrupts) {
+ size_t bytes_read = 0;
+ {
+ const gsl::span<const char> read_data =
+ receive_buffer_.PopSpan(buffer.size());
+ std::copy(read_data.begin(), read_data.end(), buffer.begin());
+ bytes_read += read_data.size();
+ }
+
+ ReenableInterrupts{disable_interrupts};
+
+ {
+ const gsl::span<const char> read_data =
+ receive_buffer_.PopSpan(buffer.size() - bytes_read);
+ std::copy(read_data.begin(), read_data.end(),
+ buffer.subspan(bytes_read).begin());
+ bytes_read += read_data.size();
+ }
+
+ return buffer.first(bytes_read);
+}
+
+bool InterruptBufferedSpi::WriteFrame(bool disable_empty,
+ const DisableInterrupts &) {
+ if (transmit_buffer_.empty()) {
+ if (disable_empty) {
+ spi_.DisableTransmitInterrupt();
+ }
+ return false;
+ }
+ if (!spi_.SpaceAvailable()) {
+ return false;
+ }
+ spi_.WriteFrame(transmit_buffer_.PopSingle());
+ return true;
+}
+
+bool InterruptBufferedSpi::ReadFrame(const DisableInterrupts &) {
+ if (!spi_.DataAvailable()) {
+ return false;
+ }
+ const auto frame = spi_.ReadFrame();
+ --frames_to_receive_;
+ if (frames_to_receive_ <= 0) {
+ spi_.DisableReceiveInterrupt();
+ }
+ if (receive_buffer_.full()) {
+ return false;
+ }
+ receive_buffer_.PushSingle(frame);
+ return true;
+}
+
+} // namespace teensy
+} // namespace frc971
diff --git a/motors/peripheral/spi.h b/motors/peripheral/spi.h
new file mode 100644
index 0000000..1cfb385
--- /dev/null
+++ b/motors/peripheral/spi.h
@@ -0,0 +1,125 @@
+#ifndef MOTORS_PERIPHERAL_SPI_H_
+#define MOTORS_PERIPHERAL_SPI_H_
+
+#include "motors/core/kinetis.h"
+#include "motors/peripheral/uart_buffer.h"
+#include "motors/util.h"
+#include "third_party/GSL/include/gsl/gsl"
+
+namespace frc971 {
+namespace teensy {
+
+// Simple synchronous interface to a SPI peripheral.
+class Spi {
+ public:
+ Spi(KINETISK_SPI_t *module, int module_clock_frequency)
+ : module_(module), module_clock_frequency_(module_clock_frequency) {}
+ Spi(const Spi &) = delete;
+ ~Spi();
+ Spi &operator=(const Spi &) = delete;
+
+ // Currently hard-coded for slave mode with the parameters we want. In the
+ // future, we should add more setters to configure things in more detail.
+ void Initialize();
+
+ // Cleras all the hardware queues.
+ void ClearQueues();
+
+ bool SpaceAvailable() const { return module_->SR & M_SPI_TFFF; }
+ // Only call this if SpaceAvailable() has just returned true.
+ void WriteFrame(uint32_t frame_flags) {
+ module_->PUSHR = frame_flags;
+ module_->SR = M_SPI_TFFF;
+ }
+
+ bool DataAvailable() const { return module_->SR & M_SPI_RFDF; }
+ // Only call this if DataAvailable() has just returned true.
+ uint16_t ReadFrame() {
+ const uint16_t result = module_->POPR;
+ module_->SR = M_SPI_RFDF;
+ return result;
+ }
+
+ // Calling code must synchronize all of these.
+ void EnableTransmitInterrupt() {
+ rser_value_ |= M_SPI_TFFF_RE;
+ }
+ void DisableTransmitInterrupt() {
+ rser_value_ &= ~M_SPI_TFFF_RE;
+ }
+ void EnableReceiveInterrupt() {
+ rser_value_ |= M_SPI_RFDF_RE;
+ }
+ void DisableReceiveInterrupt() {
+ rser_value_ &= ~M_SPI_RFDF_RE;
+ }
+ void FlushInterruptRequests() {
+ module_->RSER = rser_value_;
+ }
+
+ private:
+ KINETISK_SPI_t *const module_;
+ const int module_clock_frequency_;
+
+ // What we put in RSER.
+ uint32_t rser_value_ = 0;
+
+ // What we put in MCR.
+ uint32_t mcr_value_ = 0;
+};
+
+// Interrupt-based buffered interface to a SPI peripheral.
+class InterruptBufferedSpi {
+ public:
+ InterruptBufferedSpi(KINETISK_SPI_t *module, int module_clock_frequency)
+ : spi_(module, module_clock_frequency) {}
+ ~InterruptBufferedSpi();
+
+ // Provides access to the underlying Spi wrapper for configuration. Don't do
+ // anything else with this object besides configure it.
+ Spi *spi() { return &spi_; }
+
+ void Initialize();
+
+ // Clears all of the queues, in both hardware and software.
+ //
+ // Note that this still leaves a to-be-transmitted byte queued.
+ void ClearQueues(const DisableInterrupts &);
+
+ // Queues up the given data for immediate writing. Blocks only if the queue
+ // fills up before all of data is enqueued.
+ void Write(gsl::span<const char> data, DisableInterrupts *disable_interrupts);
+
+ // Reads currently available data.
+ // Returns all the data which is currently available (possibly none);
+ // buffer is where to store the result. The return value will be a subspan of
+ // this.
+ gsl::span<char> Read(gsl::span<char> buffer,
+ DisableInterrupts *disable_interrupts);
+
+ // Should be called as the body of the interrupt handler.
+ void HandleInterrupt(const DisableInterrupts &disable_interrupts) {
+ while (ReadAndWriteFrame(true, disable_interrupts)) {}
+ spi_.FlushInterruptRequests();
+ }
+
+ private:
+ bool WriteFrame(bool disable_empty, const DisableInterrupts &);
+ bool ReadFrame(const DisableInterrupts &);
+ bool ReadAndWriteFrame(bool disable_empty,
+ const DisableInterrupts &disable_interrupts) {
+ const bool read = ReadFrame(disable_interrupts);
+ const bool written = WriteFrame(disable_empty, disable_interrupts);
+ return read || written;
+ }
+
+ Spi spi_;
+ UartBuffer<1024> transmit_buffer_, receive_buffer_;
+ // The number of frames we should receive before stopping.
+ int frames_to_receive_ = 0;
+};
+
+} // namespace teensy
+} // namespace frc971
+
+#endif // MOTORS_PERIPHERAL_SPI_H_
diff --git a/motors/peripheral/uart_buffer.h b/motors/peripheral/uart_buffer.h
index 879ab5c..19015bb 100644
--- a/motors/peripheral/uart_buffer.h
+++ b/motors/peripheral/uart_buffer.h
@@ -15,7 +15,7 @@
// Returns the number of characters added.
__attribute__((warn_unused_result)) int PushSpan(gsl::span<const char> data);
- // max is the maximum size the returned spans should be.
+ // max is the maximum size the returned span should be.
// The data in the result is only valid until another method is called.
// Note that this may not return all available data when doing so would
// require wrapping around, but it will always return a non-empty span if any
@@ -25,6 +25,8 @@
bool empty() const { return size_ == 0; }
bool full() const { return size_ == kSize; }
+ void clear() { size_ = 0; }
+
// This may only be called when !empty().
char PopSingle();
// This may only be called when !full().