Add Kinetis-K UART code
This was used for bringing up fet12v2 already, and seems to work nicely.
Need to either document how to use stty to configure a serial port to
interact with this or switch it to use Linux's defaults.
Change-Id: Id13bc733a53f5c723cce56b1c63c69070039fbcd
diff --git a/motors/peripheral/BUILD b/motors/peripheral/BUILD
index 10fef28..4449dc2 100644
--- a/motors/peripheral/BUILD
+++ b/motors/peripheral/BUILD
@@ -1,43 +1,78 @@
load("//tools:environments.bzl", "mcu_cpus")
cc_library(
- name = 'adc',
- visibility = ['//visibility:public'],
- hdrs = [
- 'adc.h',
- ],
- srcs = [
- 'adc.cc',
- ],
- deps = [
- ':configuration',
- '//motors:util',
- '//motors/core',
- ],
- restricted_to = mcu_cpus,
+ name = "adc",
+ srcs = [
+ "adc.cc",
+ ],
+ hdrs = [
+ "adc.h",
+ ],
+ restricted_to = mcu_cpus,
+ visibility = ["//visibility:public"],
+ deps = [
+ ":configuration",
+ "//motors:util",
+ "//motors/core",
+ ],
)
cc_library(
- name = 'configuration',
- visibility = ['//visibility:public'],
- hdrs = [
- 'configuration.h',
- ],
- restricted_to = mcu_cpus,
+ name = "configuration",
+ hdrs = [
+ "configuration.h",
+ ],
+ restricted_to = mcu_cpus,
+ visibility = ["//visibility:public"],
)
cc_library(
- name = 'can',
- visibility = ['//visibility:public'],
- hdrs = [
- 'can.h',
- ],
- srcs = [
- 'can.c',
- ],
- deps = [
- '//motors/core',
- '//motors:util',
- ],
- restricted_to = mcu_cpus,
+ name = "can",
+ srcs = [
+ "can.c",
+ ],
+ hdrs = [
+ "can.h",
+ ],
+ restricted_to = mcu_cpus,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//motors:util",
+ "//motors/core",
+ ],
+)
+
+cc_library(
+ name = "uart_buffer",
+ hdrs = ["uart_buffer.h"],
+ compatible_with = mcu_cpus,
+ visibility = ["//visibility:public"],
+ deps = [
+ "//third_party/GSL",
+ ],
+)
+
+cc_test(
+ name = "uart_buffer_test",
+ srcs = [
+ "uart_buffer_test.cc",
+ ],
+ deps = [
+ ":uart_buffer",
+ "//aos/testing:googletest",
+ ],
+)
+
+cc_library(
+ name = "uart",
+ srcs = ["uart.cc"],
+ hdrs = ["uart.h"],
+ restricted_to = mcu_cpus,
+ visibility = ["//visibility:public"],
+ deps = [
+ ":uart_buffer",
+ "//motors:util",
+ "//motors/core",
+ "//third_party/GSL",
+ ],
)
diff --git a/motors/peripheral/uart.cc b/motors/peripheral/uart.cc
new file mode 100644
index 0000000..f910651
--- /dev/null
+++ b/motors/peripheral/uart.cc
@@ -0,0 +1,97 @@
+#include "motors/peripheral/uart.h"
+
+#include <stdint.h>
+
+namespace frc971 {
+namespace teensy {
+
+// Currently hard-coded for 8-bit + odd parity + start bit + stop bit.
+void Uart::Initialize(int baud_rate) {
+ {
+ // UART baud rate = UART module clock / (16 * (SBR[12:0] + BRFD))
+ // BRFD = BRFA (bitfield) / 32
+ const int desired_receiver_clock = baud_rate * 16;
+ const int sbr_and_brfd32 =
+ ((static_cast<int64_t>(module_clock_frequency_) * UINT64_C(64) /
+ static_cast<int64_t>(desired_receiver_clock)) +
+ 1) /
+ 2;
+ const int sbr = sbr_and_brfd32 / 32;
+ const int brfa = sbr_and_brfd32 % 32;
+
+ module_->BDH = (sbr >> 8) & 0x1F;
+ module_->BDL = sbr & 0xFF;
+ module_->C1 = M_UART_M /* 9 data bits */ |
+ M_UART_ILT /* only detect idle after stop bit */ |
+ M_UART_PE /* enable parity */ | M_UART_PT /* odd parity */;
+ module_->C4 = V_UART_BRFA(brfa);
+ }
+ {
+ const uint8_t pfifo = module_->PFIFO;
+ tx_fifo_size_ = G_UART_TXFIFOSIZE(pfifo);
+ rx_fifo_size_ = G_UART_RXFIFOSIZE(pfifo);
+ }
+
+ // When C1[M] is set and C4[M10] is cleared, the UART is configured for 9-bit
+ // data characters. If C1[PE] is enabled, the ninth bit is either C3[T8/R8] or
+ // the internally generated parity bit
+
+ // TODO(Brian): M_UART_TIE /* Enable TX interrupt or DMA */ |
+ // M_UART_RIE /* Enable RX interrupt or DMA */
+ // Also set in C5: M_UART_TDMAS /* Do DMA for TX */ |
+ // M_UART_RDMAS /* Do DMA for RX */
+ c2_value_ = M_UART_TE;
+ module_->C2 = c2_value_;
+ module_->PFIFO =
+ M_UART_TXFE /* Enable TX FIFO */ | M_UART_RXFE /* Enable RX FIFO */;
+ module_->CFIFO =
+ M_UART_TXFLUSH /* Flush TX FIFO */ | M_UART_RXFLUSH /* Flush RX FIFO */;
+ // TODO(Brian): Adjust for DMA?
+ module_->TWFIFO = tx_fifo_size_ - 1;
+ module_->RWFIFO = rx_fifo_size_ - 1;
+}
+
+void Uart::DoWrite(gsl::span<char> data) {
+ for (int i = 0; i < data.size(); ++i) {
+ while (!SpaceAvailable()) {
+ }
+ WriteCharacter(data[i]);
+ }
+}
+
+Uart::~Uart() { DisableTransmitInterrupt(); }
+
+void InterruptBufferedUart::Initialize(int baud_rate) {
+ uart_.Initialize(baud_rate);
+}
+
+void InterruptBufferedUart::Write(gsl::span<char> data,
+ const DisableInterrupts &disable_interrupts) {
+ uart_.EnableTransmitInterrupt();
+ static_assert(buffer_.size() >= 8,
+ "Need enough space to not turn the interrupt off each time");
+ while (!data.empty()) {
+ const int bytes_written = buffer_.PushSpan(data);
+ data = data.subspan(bytes_written);
+ WriteCharacters(data.empty(), disable_interrupts);
+ }
+}
+
+void InterruptBufferedUart::WriteCharacters(bool disable_empty,
+ const DisableInterrupts &) {
+ while (true) {
+ if (buffer_.empty()) {
+ if (disable_empty) {
+ uart_.DisableTransmitInterrupt();
+ }
+ return;
+ }
+ if (!uart_.SpaceAvailable()) {
+ return;
+ }
+ uart_.WriteCharacter(buffer_.PopSingle());
+ }
+}
+
+} // namespace teensy
+} // namespace frc971
diff --git a/motors/peripheral/uart.h b/motors/peripheral/uart.h
new file mode 100644
index 0000000..cf79815
--- /dev/null
+++ b/motors/peripheral/uart.h
@@ -0,0 +1,78 @@
+#ifndef MOTORS_PERIPHERAL_UART_H_
+#define MOTORS_PERIPHERAL_UART_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 UART.
+class Uart {
+ public:
+ Uart(KINETISK_UART_t *module, int module_clock_frequency)
+ : module_(module), module_clock_frequency_(module_clock_frequency) {}
+ Uart(const Uart &) = delete;
+ ~Uart();
+ Uart &operator=(const Uart &) = delete;
+
+ void Initialize(int baud_rate);
+
+ // Blocks until all of the data is at least queued.
+ void Write(gsl::span<char> data, const DisableInterrupts &) { DoWrite(data); }
+
+ bool SpaceAvailable() const { return module_->S1 & M_UART_TDRE; }
+ // Only call this if SpaceAvailable() has just returned true.
+ void WriteCharacter(char c) { module_->D = c; }
+
+ void EnableTransmitInterrupt() {
+ c2_value_ |= M_UART_TIE;
+ module_->C2 = c2_value_;
+ }
+
+ void DisableTransmitInterrupt() {
+ c2_value_ &= ~M_UART_TIE;
+ module_->C2 = c2_value_;
+ }
+
+ private:
+ void DoWrite(gsl::span<char> data);
+
+ KINETISK_UART_t *const module_;
+ const int module_clock_frequency_;
+ // What we put in C2 except TE.
+ uint8_t c2_value_;
+
+ int tx_fifo_size_, rx_fifo_size_;
+};
+
+// Interrupt-based buffered interface to a UART.
+class InterruptBufferedUart {
+ public:
+ InterruptBufferedUart(KINETISK_UART_t *module, int module_clock_frequency)
+ : uart_(module, module_clock_frequency) {}
+
+ void Initialize(int baud_rate);
+
+ void Write(gsl::span<char> data, const DisableInterrupts &disable_interrupts);
+
+ // Should be called as the body of the interrupt handler.
+ void HandleInterrupt(const DisableInterrupts &disable_interrupts) {
+ WriteCharacters(true, disable_interrupts);
+ }
+
+ private:
+ void WriteCharacters(bool disable_empty, const DisableInterrupts &);
+
+ Uart uart_;
+ UartBuffer<1024> buffer_;
+
+ bool interrupt_enabled_ = false;
+};
+
+} // namespace teensy
+} // namespace frc971
+
+#endif // MOTORS_PERIPHERAL_UART_H_
diff --git a/motors/peripheral/uart_buffer.h b/motors/peripheral/uart_buffer.h
new file mode 100644
index 0000000..fb7d126
--- /dev/null
+++ b/motors/peripheral/uart_buffer.h
@@ -0,0 +1,66 @@
+#ifndef MOTORS_PERIPHERAL_UART_BUFFER_H_
+#define MOTORS_PERIPHERAL_UART_BUFFER_H_
+
+#include <array>
+
+#include "third_party/GSL/include/gsl/gsl"
+
+namespace frc971 {
+namespace teensy {
+
+// Manages a circular buffer of data to send out.
+template<int kSize>
+class UartBuffer {
+ public:
+ // Returns the number of characters added.
+ __attribute__((warn_unused_result)) int PushSpan(gsl::span<char> data);
+
+ bool empty() const { return size_ == 0; }
+
+ // This may only be called when !empty().
+ char PopSingle();
+
+ static constexpr int size() { return kSize; }
+
+ private:
+ // The index at which we will push the next character.
+ int start_ = 0;
+ // How many characters we currently have.
+ int size_ = 0;
+
+ ::std::array<char, kSize> data_;
+};
+
+template<int kSize>
+int UartBuffer<kSize>::PushSpan(gsl::span<char> data) {
+ const int end_location = (start_ + size_) % kSize;
+ const int remaining_end = ::std::min(kSize - size_, kSize - end_location);
+ const int on_end = ::std::min<int>(data.size(), remaining_end);
+ if (on_end > 0) {
+ memcpy(&data_[end_location], data.data(), on_end);
+ }
+ size_ += on_end;
+ const int not_on_end = data.size() - on_end;
+ if (not_on_end == 0) {
+ return data.size();
+ }
+
+ const int remaining_start = ::std::min(kSize - size_, start_);
+ const int on_start = ::std::min(not_on_end, remaining_start);
+ memcpy(data_.data(), &data[on_end], on_start);
+ size_ += on_start;
+ return on_end + on_start;
+}
+
+template<int kSize>
+char UartBuffer<kSize>::PopSingle() {
+ const char r = data_[start_];
+ --size_;
+ start_ = (start_ + 1) % kSize;
+ return r;
+}
+
+} // namespace teensy
+} // namespace frc971
+
+#endif // MOTORS_PERIPHERAL_UART_BUFFER_H_
diff --git a/motors/peripheral/uart_buffer_test.cc b/motors/peripheral/uart_buffer_test.cc
new file mode 100644
index 0000000..5ab7c98
--- /dev/null
+++ b/motors/peripheral/uart_buffer_test.cc
@@ -0,0 +1,186 @@
+#include "motors/peripheral/uart_buffer.h"
+
+#include "gtest/gtest.h"
+
+namespace frc971 {
+namespace teensy {
+namespace testing {
+
+// Tests that using PushSpan with single characters works correctly.
+TEST(UartBufferTest, PushSpanSingle) {
+ UartBuffer<1024> buffer;
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 1> data{{1}};
+ ASSERT_EQ(1, buffer.PushSpan(data));
+ }
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(1, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 1> data{{2}};
+ ASSERT_EQ(1, buffer.PushSpan(data));
+ }
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(2, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+}
+
+// Tests that using PushSpan with multiple characters works correctly.
+TEST(UartBufferTest, PushSpanMultiple) {
+ UartBuffer<1024> buffer;
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 4> data{{1, 2, 4, 8}};
+ ASSERT_EQ(4, buffer.PushSpan(data));
+ }
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(1, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(2, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(4, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(8, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+}
+
+// Tests that using PushSpan works correctly when wrapping with a
+// multiple-character push.
+TEST(UartBufferTest, PushSpanWrapMultiple) {
+ UartBuffer<4> buffer;
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 2> data{{10, 20}};
+ ASSERT_EQ(2, buffer.PushSpan(data));
+ }
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(10, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(20, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 4> data{{1, 2, 4, 8}};
+ ASSERT_EQ(4, buffer.PushSpan(data));
+ }
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(1, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(2, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(4, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(8, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+}
+
+// Tests that using PushSpan works correctly when wrapping with a
+// single-character push.
+TEST(UartBufferTest, PushSpanWrapSingle) {
+ UartBuffer<3> buffer;
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 3> data{{10, 20, 30}};
+ ASSERT_EQ(3, buffer.PushSpan(data));
+ }
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(10, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+
+ {
+ ::std::array<char, 1> data{{1}};
+ ASSERT_EQ(1, buffer.PushSpan(data));
+ }
+
+ ASSERT_EQ(20, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(30, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(1, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+}
+
+// Tests that using PushSpan works correctly when overflowing with a
+// multiple-character push, where none of it fits.
+TEST(UartBufferTest, PushSpanOverflowAllMultiple) {
+ UartBuffer<2> buffer;
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 2> data{{10, 20}};
+ ASSERT_EQ(2, buffer.PushSpan(data));
+ }
+
+ {
+ ::std::array<char, 4> data{{1, 2, 4, 8}};
+ ASSERT_EQ(0, buffer.PushSpan(data));
+ }
+
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(10, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(20, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+}
+
+// Tests that using PushSpan works correctly when overflowing with a
+// multiple-character push, where some of it fits.
+TEST(UartBufferTest, PushSpanOverflowSomeMultiple) {
+ UartBuffer<4> buffer;
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 2> data{{10, 20}};
+ ASSERT_EQ(2, buffer.PushSpan(data));
+ }
+ ASSERT_FALSE(buffer.empty());
+
+ {
+ ::std::array<char, 4> data{{1, 2, 4, 8}};
+ ASSERT_EQ(2, buffer.PushSpan(data));
+ }
+
+ ASSERT_EQ(10, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(20, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(1, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(2, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+}
+
+// Tests that using PushSpan works correctly when overflowing with a
+// single-character push.
+TEST(UartBufferTest, PushSpanOverflowSingle) {
+ UartBuffer<3> buffer;
+ ASSERT_TRUE(buffer.empty());
+
+ {
+ ::std::array<char, 3> data{{10, 20, 30}};
+ ASSERT_EQ(3, buffer.PushSpan(data));
+ }
+ ASSERT_FALSE(buffer.empty());
+
+ {
+ ::std::array<char, 1> data{{1}};
+ ASSERT_EQ(0, buffer.PushSpan(data));
+ }
+
+ ASSERT_EQ(10, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(20, buffer.PopSingle());
+ ASSERT_FALSE(buffer.empty());
+ ASSERT_EQ(30, buffer.PopSingle());
+ ASSERT_TRUE(buffer.empty());
+}
+
+} // namespace testing
+} // namespace teensy
+} // namespace frc971