diff options
| -rw-r--r-- | ThrottleQuadrant.cpp | 59 | ||||
| -rw-r--r-- | ThrottleQuadrant.hpp | 49 | ||||
| -rw-r--r-- | protocol.cpp | 124 | ||||
| -rw-r--r-- | protocol.hpp | 88 | ||||
| -rw-r--r-- | system.cpp | 11 | ||||
| -rw-r--r-- | system.hpp | 18 | ||||
| -rw-r--r-- | throttle-quadrant.ino | 42 | ||||
| -rw-r--r-- | utilities.cpp | 5 | ||||
| -rw-r--r-- | utilities.hpp | 67 |
9 files changed, 125 insertions, 338 deletions
diff --git a/ThrottleQuadrant.cpp b/ThrottleQuadrant.cpp new file mode 100644 index 0000000..4e4404a --- /dev/null +++ b/ThrottleQuadrant.cpp @@ -0,0 +1,59 @@ +#include "ThrottleQuadrant.hpp" + +#include <limits.h> + +#include <Arduino.h> + +auto SaitekThrottleQuadrant::beginTransaction() -> void { + noInterrupts(); + digitalWrite(latch_pin, LOW); + delayMicroseconds(latchDelayMicros); +} + +auto SaitekThrottleQuadrant::endTransaction() -> void { + digitalWrite(latch_pin, HIGH); + delayMicroseconds(latchDelayMicros); + interrupts(); +} + +template<typename T, size_t Bits> +auto SaitekThrottleQuadrant::doRead() -> T { + auto value = T{}; + + for (auto i = 0u; i < Bits; ++i) { + digitalWrite(clock_pin, LOW); + delayMicroseconds(clockDelayMicros); + + value |= digitalRead(data_pin) << i; + + digitalWrite(clock_pin, HIGH); + delayMicroseconds(clockDelayMicros); + } + + return value; +} + +auto SaitekThrottleQuadrant::begin(unsigned clock_pin, unsigned latch_pin, unsigned data_pin) -> void { + clock_pin = clock_pin; + latch_pin = latch_pin; + data_pin = data_pin; + + pinMode(clock_pin, OUTPUT); + pinMode(latch_pin, OUTPUT); + pinMode(data_pin, INPUT); +} + +auto SaitekThrottleQuadrant::read() noexcept -> Data { + auto result = Data{}; + + beginTransaction(); + + result.left = doRead<uint8_t>(); + result.middle = doRead<uint8_t>(); + result.right = doRead<uint8_t>(); + result.buttons = doRead<uint16_t, numberOfButtons>(); + + endTransaction(); + + return result; +} diff --git a/ThrottleQuadrant.hpp b/ThrottleQuadrant.hpp new file mode 100644 index 0000000..8776aab --- /dev/null +++ b/ThrottleQuadrant.hpp @@ -0,0 +1,49 @@ +#ifndef SAITEK_THROTTLE_QUADRANT_HPP +#define SAITEK_THROTTLE_QUADRANT_HPP + +#include <limits.h> +#include <stddef.h> +#include <stdint.h> + +struct SaitekThrottleQuadrant { + + //! The data read from the throttle quadrant. + struct [[gnu::packed]] Data { + uint8_t left; + uint8_t right; + uint8_t middle; + uint16_t buttons : 9; + uint16_t : 0; + }; + + //! The minimum delay between successive read operations. + auto constexpr static readDelayMillis = 25u; + + //! Initialize the throttle quadrant interface. + auto begin(unsigned clock_pin, unsigned latch_pin, unsigned data_pin) noexcept -> void; + + //! Read the current state of the throttle quadrant. + auto read() noexcept -> Data; + +private: + //! The time for the clock to propagate. + auto constexpr static clockDelayMicros = 25u; + + //! The time for the latch to settle. + auto constexpr static latchDelayMicros = 5u; + + //! The number of buttons on the throttle quadrant. + auto constexpr static numberOfButtons = 9u; + + auto beginTransaction() -> void; + auto endTransaction() -> void; + + template<typename T, size_t Bits = sizeof(T) * CHAR_BIT> + auto doRead() -> T; + + unsigned clock_pin{}; + unsigned latch_pin{}; + unsigned data_pin{}; +} static ThrottleQuadrant; + +#endif
\ No newline at end of file diff --git a/protocol.cpp b/protocol.cpp deleted file mode 100644 index 5ad0975..0000000 --- a/protocol.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#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 diff --git a/protocol.hpp b/protocol.hpp deleted file mode 100644 index c71b7d3..0000000 --- a/protocol.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef THROTTLE_QUADRANT_PROTOCOL_HPP -#define THROTTLE_QUADRANT_PROTOCOL_HPP - -#include "utilities.hpp" - -#include <Arduino.h> - -//! All our custom code lives in its own name space to avoid collisions. -namespace tq::protocol { - -//! A message as received from the throttle quadrant. -struct [[gnu::packed]] message { - //! The current throttle value (unit TBD). - byte throttle; - //! The current propeller speed value (unit TBD). - byte propeller_speed; - //! The current mixture value (unit TBD). - byte mixture; - - //! The state (pressed/released) of the first trigger. - byte trigger_1 : 1; - //! The state (pressed/released) of the second trigger. - byte trigger_2 : 1; - //! The state (pressed/released) of the third trigger. - byte trigger_3 : 1; - //! The state (pressed/released) of the fourth trigger. - byte trigger_4 : 1; - //! The state (pressed/released) of the fifth trigger. - byte trigger_5 : 1; - //! The state (pressed/released) of the sixth trigger. - byte trigger_6 : 1; - - //! Whether the throttle is idle or not. - byte throttle_idle : 1; - - //! Whether the throttle is feathered or not (?). - byte propeller_feather : 1; - - //! Whether the mixture is cut off or not. - byte mixture_cutoff : 1; - - //! Reserved bits. - byte : 7; -}; - -//! Initialize the protocol handling subsystem. -auto init() -> void; - -//! Start a run of the protocol handling subsystem. -//! -//! A new run is only started if there is not current run ongoing. -//! -//! @return true iff. a new run was started, false otherwise. -auto start() -> bool; - -//! Perform the synchronous SPI transfer, if one is pending. -//! -//! Perform the final synchronous transfer from the throttle quadrant. If no -//! new data is available, an empty optional is returned. -//! -//! @return An optional object containing a new message if one is available. -auto perform_transfer() -> optional<message>; - -//! @internal -//! Process the "data ready" signal coming from the throttle quadrant. -//! -//! When the throttle quadrant pulls the "data ready" line low, we know that -//! there is new data available to be read out. We must adhere to a somewhat -//! strict timing protocol, as detailed in the implemenation of this function. -auto on_data_ready() -> void; - -//! @internal -//! Process the "start delay" signal coming from the throttle quadrant. -//! -//! After notifying us about the data being ready, the "data ready" line will -//! be deasserted. However, we must wait an additional 1.2ms before we start -//! the transfer. -auto on_start_delay() -> void; - -//! @internal -//! Process the end of the start delay. -//! -//! The data is of the throttle quadrant is now ready to be read. -auto on_start_delay_passed() -> void; - -} - -#endif
\ No newline at end of file diff --git a/system.cpp b/system.cpp deleted file mode 100644 index d130d66..0000000 --- a/system.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "system.hpp" - -#include <Arduino.h> - -namespace tq::system { - -// Abort compilation if the interrupt pin is not valid -static_assert(digitalPinToInterrupt(data_ready_signal_pin) != -1, - "The selected pin is not a valid interrupt source!"); - -}
\ No newline at end of file diff --git a/system.hpp b/system.hpp deleted file mode 100644 index e39b79e..0000000 --- a/system.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef THROTTLE_QUADRANT_SYSTEM_HPP -#define THROTTLE_QUADRANT_SYSTEM_HPP - -#include <Arduino.h> - -//! All our custom code lives in its own name space to avoid collisions. -namespace tq::system { - -//! The pin to which the "data ready" line of the throttle quadrant is connected. -//! -//! Since the throttle qudrant runs its logic asynchronously to the execution -//! of our code, it exposes a signal to inform us that it has acquired new -//! data. It does so by pulling the "data ready" line low. -auto constexpr data_ready_signal_pin = 2; - -} - -#endif
\ No newline at end of file diff --git a/throttle-quadrant.ino b/throttle-quadrant.ino index 7c213a1..54740d9 100644 --- a/throttle-quadrant.ino +++ b/throttle-quadrant.ino @@ -1,32 +1,24 @@ -#include "protocol.hpp" +#include "ThrottleQuadrant.hpp" -//! Set up any global state. -//! -//! This function is executed once during the start of the firmware. -auto setup() -> void { - // Initialize the protocol subsystem. - tq::protocol::init(); +namespace { - // Initialize the serial output stream. - Serial.begin(9600); -} +//! The time point (in milliseconds since boot) when we last read out the data of the throttle quadrant. +auto lastReadTimePoint = 0ul; -//! Perform one iteration through the firmare logic. -//! -//! This function is called in an endless loop after the firmware has started. -auto loop() -> void { - // Always try to start a new transfer. - tq::protocol::start(); +} - // Receive a new message if one is available. - auto maybe_message = tq::protocol::perform_transfer(); +void setup() { + Serial.begin(115200); + ThrottleQuadrant.begin(2, 3, 4); +} - if (maybe_message.has_value()) { - // Extract the received message. - auto const& message = maybe_message.value(); +void loop() { + auto currentTimePoint = millis(); - Serial.write("new message: "); - Serial.write("throttle == "); - Serial.print(static_cast<int>(message.throttle)); + // Read out the throttle quadrant if the minimum amount of wait time has passed. + if ((currentTimePoint - lastReadTimePoint) >= ThrottleQuadrant.readDelayMillis) { + auto data = ThrottleQuadrant.read(); + lastReadTimePoint = millis(); + Serial.write(reinterpret_cast<byte const *>(&data), sizeof(data)); } -}
\ No newline at end of file +} diff --git a/utilities.cpp b/utilities.cpp deleted file mode 100644 index d05e67c..0000000 --- a/utilities.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include <Arduino.h> - -void* operator new(size_t size, void* ptr) { - return ptr; -}
\ No newline at end of file diff --git a/utilities.hpp b/utilities.hpp deleted file mode 100644 index 819c170..0000000 --- a/utilities.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef THROTTLE_QUADRANT_UTILITIES_HPP -#define THROTTLE_QUADRANT_UTILITIES_HPP - -#include <Arduino.h> - -void* operator new(size_t size, void* ptr); - -namespace tq { - -template<typename ValueType> -struct optional { - - optional() - : m_engaged{ false } { - } - - optional(optional const& other) - : m_engaged{ other.has_value() } { - if (m_engaged) { - construct_from(other.value()); - } - } - - explicit optional(ValueType const& value) - : m_engaged{ true } { - construct_from(value); - } - - ~optional() { - destroy(); - } - - auto operator=(optional const& other) -> optional& { - destroy(); - m_engaged = other.m_engaged; - if (m_engaged) { - construct_from(other.value()); - } - } - - auto has_value() const -> bool { - return m_engaged; - } - - auto value() const -> ValueType const& { - return *(reinterpret_cast<ValueType const*>(m_storage)); - } - -private: - auto construct_from(ValueType const& value) -> void { - new (static_cast<byte*>(m_storage)) ValueType{ value }; - } - - auto destroy() -> void { - if (has_value()) { - (reinterpret_cast<ValueType const*>(m_storage))->~ValueType(); - } - } - - alignas(ValueType) byte m_storage[sizeof(ValueType)]; - bool m_engaged; -}; - - -} - -#endif
\ No newline at end of file |
