summaryrefslogtreecommitdiff
path: root/protocol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocol.cpp')
-rw-r--r--protocol.cpp124
1 files changed, 124 insertions, 0 deletions
diff --git a/protocol.cpp b/protocol.cpp
new file mode 100644
index 0000000..5ad0975
--- /dev/null
+++ b/protocol.cpp
@@ -0,0 +1,124 @@
+#include "protocol.hpp"
+#include "system.hpp"
+
+#include <Arduino.h>
+#include <TimerOne.h>
+#include <SPI.h>
+
+// ============================================================================
+// The throttle qudrant requires a specific protocol for communication:
+// 1. At time 0: the "data ready" line will be pulled low.
+// 2. At 0.7ms: the line will is release.
+// 3. At 1.9ms: the data is ready to be read.
+//
+// The data can be read out at about 20 kHz. It is presented LSB first and comprises 5
+// bytes.
+// ============================================================================
+
+namespace tq::protocol {
+
+namespace state {
+
+//! Whether or not an SPI transfer is pending.
+auto volatile spi_transfer_pending = false;
+
+//! Whether we are currently in a protocol loop.
+auto volatile protocol_loop_running = false;
+
+}
+
+namespace settings {
+
+//! The SPI bus configuration for data transfers from the throttle quadrant.
+auto const spi_configuration = SPISettings{ 20000, LSBFIRST, SPI_MODE3 };
+
+}
+
+//! Start a transfer of the throttle quadrant data.
+//!
+//! This function schedules the next step in the protocol.
+auto begin_transfer_cycle() -> void {
+ // Disable the "data ready" interrupt, since we need to reuse it for the next step.
+ detachInterrupt(digitalPinToInterrupt(system::data_ready_signal_pin));
+
+ // Attach an inverted "data ready" interrupt, so we know when to start the timer.
+ attachInterrupt(digitalPinToInterrupt(system::data_ready_signal_pin),
+ on_start_delay,
+ RISING);
+}
+
+auto init() -> void {
+ // Ensure we are not starting spurious transfers.
+ state::spi_transfer_pending = false;
+ state::protocol_loop_running = false;
+
+ // Prepare the protocol timer.
+ Timer1.initialize();
+
+ // Prepare the "data ready" signal pin.
+ pinMode(system::data_ready_signal_pin, INPUT_PULLUP);
+}
+
+auto on_data_ready() -> void {
+ // Read out the current state of the "data ready" signal pin.
+ auto pin_state = digitalRead(system::data_ready_signal_pin);
+
+ // Check if we actually are at the start of a transfer.
+ if (pin_state == LOW) {
+ begin_transfer_cycle();
+ }
+}
+
+auto on_start_delay() -> void {
+ // Disable the interrupt so we are not interrupted during a transfer.
+ detachInterrupt(digitalPinToInterrupt(system::data_ready_signal_pin));
+
+ // Wait 1.2ms until we start the actual SPI transfer.
+ Timer1.attachInterrupt(on_start_delay_passed, 1200);
+ Timer1.start();
+}
+
+auto on_start_delay_passed() -> void {
+ Timer1.detachInterrupt();
+ state::spi_transfer_pending = true;
+}
+
+auto perform_transfer() -> optional<message> {
+ byte buffer[sizeof(message)] = {};
+
+ if (!state::spi_transfer_pending) {
+ return {};
+ }
+
+ // Record that we are done with one loop.
+ state::spi_transfer_pending = false;
+ state::protocol_loop_running = false;
+
+ SPI.beginTransaction(settings::spi_configuration);
+ SPI.transfer(buffer, sizeof(buffer));
+ SPI.endTransaction();
+
+ auto parsed = message{};
+ memcpy(&parsed, buffer, sizeof(message));
+
+ return optional<message>{ parsed };
+}
+
+auto start() -> bool {
+ // If we are already in a loop, don't start a new one.
+ if (state::protocol_loop_running) {
+ return false;
+ }
+
+ // Record that a loop has started
+ state::protocol_loop_running = true;
+
+ // Attach the protocol handling subsystem entry point to the correct input line.
+ attachInterrupt(digitalPinToInterrupt(system::data_ready_signal_pin),
+ tq::protocol::on_data_ready,
+ FALLING);
+
+ return true;
+}
+
+} \ No newline at end of file