#include "protocol.hpp" #include "system.hpp" #include #include #include // ============================================================================ // 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 { 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{ 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; } }