diff options
Diffstat (limited to 'protocol.cpp')
| -rw-r--r-- | protocol.cpp | 124 |
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 |
