1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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;
}
}
|