blob: b6e66329287c4bca1ba54321d5ff2c68d2f872d9 [file] [log] [blame]
Brian Silverman246cb222019-02-02 16:38:18 -08001#include "y2019/jevois/spi.h"
2
Brian Silverman246cb222019-02-02 16:38:18 -08003#include "aos/util/bitpacking.h"
Brian Silverman0a5e7db2019-02-17 13:45:27 -08004#include "y2019/jevois/jevois_crc.h"
Brian Silvermanfdfb3132019-02-24 15:27:27 -08005#ifdef __linux__
6#include "aos/logging/logging.h"
7#else
8#define CHECK(...)
9#define CHECK_GE(...)
10#endif
Brian Silverman246cb222019-02-02 16:38:18 -080011
12// SPI transfer format (6x 8 bit frames):
13// 1. 1-byte brightness for each beacon channel.
14// 2. 1-byte specifying on/off for each light ring.
15// 3. 2-byte CRC
16//
17// SPI transfer format (41x 8 bit frames):
18// 1. Camera frame 0
19// 2. Camera frame 1
20// 3. Camera frame 2
21// 4. 2-byte CRC-16
22// Each camera frame (13x 8 bit frames):
23// 1. Duration for how old the frame is. This is a value received from the
24// camera, added to the time between the first character being received
25// by the MCU to the CS line being asserted. Specifically it's an 8 bit
26// unsigned number of ms.
27// 2. Target 0
28// 3. Target 1
29// 4. Target 2
30// Each target (4x 8 bit frames):
31// 1. 10 bits heading
32// 2. 8 bits distance
33// 3. 6 bits skew
34// 4. 6 bits height
35// 5. 1 bit target valid (a present frame has all-valid targets)
36// 6. 1 bit target present (a present frame can have from 0 to 3
37// targets, depending on how many were found)
38// Note that empty frames are still sent to indicate that the camera is
39// still working even though it doesn't see any targets.
40
41namespace frc971 {
42namespace jevois {
43namespace {
44
45constexpr float heading_min() { return -3; }
46constexpr float heading_max() { return 3; }
47constexpr int heading_bits() { return 10; }
48constexpr int heading_offset() { return 0; }
49void heading_pack(float heading, gsl::span<char> destination) {
50 const auto integer = aos::FloatToIntLinear<heading_bits()>(
51 heading_min(), heading_max(), heading);
52 aos::PackBits<uint32_t, heading_bits(), heading_offset()>(integer,
53 destination);
54}
55float heading_unpack(gsl::span<const char> source) {
56 const auto integer =
57 aos::UnpackBits<uint32_t, heading_bits(), heading_offset()>(source);
58 return aos::IntToFloatLinear<heading_bits()>(heading_min(), heading_max(),
59 integer);
60}
61
62constexpr float distance_min() { return 0; }
63constexpr float distance_max() {
64 // The field is 18.4m diagonally.
65 return 18.4;
66}
67constexpr int distance_bits() { return 8; }
68constexpr int distance_offset() { return heading_offset() + heading_bits(); }
69void distance_pack(float distance, gsl::span<char> destination) {
70 const auto integer = aos::FloatToIntLinear<distance_bits()>(
71 distance_min(), distance_max(), distance);
72 aos::PackBits<uint32_t, distance_bits(), distance_offset()>(integer,
73 destination);
74}
75float distance_unpack(gsl::span<const char> source) {
76 const auto integer =
77 aos::UnpackBits<uint32_t, distance_bits(), distance_offset()>(source);
78 return aos::IntToFloatLinear<distance_bits()>(distance_min(), distance_max(),
79 integer);
80}
81
82constexpr float skew_min() { return -3; }
83constexpr float skew_max() { return 3; }
84constexpr int skew_bits() { return 6; }
85constexpr int skew_offset() { return distance_offset() + distance_bits(); }
86void skew_pack(float skew, gsl::span<char> destination) {
87 const auto integer =
88 aos::FloatToIntLinear<skew_bits()>(skew_min(), skew_max(), skew);
89 aos::PackBits<uint32_t, skew_bits(), skew_offset()>(integer, destination);
90}
91float skew_unpack(gsl::span<const char> source) {
92 const auto integer =
93 aos::UnpackBits<uint32_t, skew_bits(), skew_offset()>(source);
94 return aos::IntToFloatLinear<skew_bits()>(skew_min(), skew_max(), integer);
95}
96
97constexpr float height_min() { return 0; }
98constexpr float height_max() { return 1.5; }
99constexpr int height_bits() { return 6; }
100constexpr int height_offset() { return skew_offset() + skew_bits(); }
101void height_pack(float height, gsl::span<char> destination) {
102 const auto integer =
103 aos::FloatToIntLinear<height_bits()>(height_min(), height_max(), height);
104 aos::PackBits<uint32_t, height_bits(), height_offset()>(integer, destination);
105}
106float height_unpack(gsl::span<const char> source) {
107 const auto integer =
108 aos::UnpackBits<uint32_t, height_bits(), height_offset()>(source);
109 return aos::IntToFloatLinear<height_bits()>(height_min(), height_max(),
110 integer);
111}
112
113constexpr int valid_bits() { return 1; }
114constexpr int valid_offset() { return height_offset() + height_bits(); }
115void valid_pack(bool valid, gsl::span<char> destination) {
116 aos::PackBits<uint32_t, valid_bits(), valid_offset()>(valid, destination);
117}
118bool valid_unpack(gsl::span<const char> source) {
119 return aos::UnpackBits<uint32_t, valid_bits(), valid_offset()>(source);
120}
121
122constexpr int present_bits() { return 1; }
123constexpr int present_offset() { return valid_offset() + valid_bits(); }
124void present_pack(bool present, gsl::span<char> destination) {
125 aos::PackBits<uint32_t, present_bits(), present_offset()>(present,
126 destination);
127}
128bool present_unpack(gsl::span<const char> source) {
129 return aos::UnpackBits<uint32_t, present_bits(), present_offset()>(source);
130}
131
132constexpr int next_offset() { return present_offset() + present_bits(); }
133static_assert(next_offset() <= 32, "Target is too big");
134
135} // namespace
136
137SpiTransfer SpiPackToRoborio(const TeensyToRoborio &message) {
138 SpiTransfer transfer;
139 gsl::span<char> remaining_space = transfer;
140 for (int frame = 0; frame < 3; ++frame) {
141 for (int target = 0; target < 3; ++target) {
142 remaining_space[0] = 0;
143 remaining_space[1] = 0;
144 remaining_space[2] = 0;
145 remaining_space[3] = 0;
146
147 if (static_cast<int>(message.frames.size()) > frame) {
148 valid_pack(true, remaining_space);
149 if (static_cast<int>(message.frames[frame].targets.size()) > target) {
150 heading_pack(message.frames[frame].targets[target].heading,
151 remaining_space);
152 distance_pack(message.frames[frame].targets[target].distance,
153 remaining_space);
154 skew_pack(message.frames[frame].targets[target].skew,
155 remaining_space);
156 height_pack(message.frames[frame].targets[target].height,
157 remaining_space);
158 present_pack(true, remaining_space);
159 } else {
160 present_pack(false, remaining_space);
161 }
162 } else {
163 valid_pack(false, remaining_space);
164 }
165
166 remaining_space = remaining_space.subspan(4);
167 }
168 if (static_cast<int>(message.frames.size()) > frame) {
169 const uint8_t age_count = message.frames[frame].age.count();
170 memcpy(&remaining_space[0], &age_count, 1);
171 } else {
172 remaining_space[0] = 0;
173 }
174 remaining_space = remaining_space.subspan(1);
175 }
176 {
Brian Silverman0a5e7db2019-02-17 13:45:27 -0800177 uint16_t crc = jevois_crc_init();
178 crc = jevois_crc_update(crc, transfer.data(),
179 transfer.size() - remaining_space.size());
180 crc = jevois_crc_finalize(crc);
Brian Silverman2eb89762019-02-17 15:16:37 -0800181 CHECK_GE(static_cast<size_t>(remaining_space.size()), sizeof(crc));
Brian Silverman246cb222019-02-02 16:38:18 -0800182 memcpy(&remaining_space[0], &crc, sizeof(crc));
183 remaining_space = remaining_space.subspan(sizeof(crc));
184 }
Brian Silverman2eb89762019-02-17 15:16:37 -0800185 CHECK(remaining_space.empty());
Brian Silverman246cb222019-02-02 16:38:18 -0800186 return transfer;
187}
188
Brian Silvermana10b87e2019-02-24 15:20:07 -0800189tl::optional<TeensyToRoborio> SpiUnpackToRoborio(
190 gsl::span<const char, spi_transfer_size()> transfer) {
Brian Silverman246cb222019-02-02 16:38:18 -0800191 TeensyToRoborio message;
192 gsl::span<const char> remaining_input = transfer;
193 for (int frame = 0; frame < 3; ++frame) {
194 const bool have_frame = valid_unpack(remaining_input);
195 if (have_frame) {
196 message.frames.push_back({});
197 }
198 for (int target = 0; target < 3; ++target) {
199 if (present_unpack(remaining_input)) {
200 if (have_frame) {
201 message.frames.back().targets.push_back({});
202 message.frames.back().targets.back().heading =
203 heading_unpack(remaining_input);
204 message.frames.back().targets.back().distance =
205 distance_unpack(remaining_input);
206 message.frames.back().targets.back().skew =
207 skew_unpack(remaining_input);
208 message.frames.back().targets.back().height =
209 height_unpack(remaining_input);
210 }
211 }
212
213 remaining_input = remaining_input.subspan(4);
214 }
215 if (have_frame) {
216 uint8_t age_count;
217 memcpy(&age_count, &remaining_input[0], 1);
218 message.frames.back().age = camera_duration(age_count);
219 }
220 remaining_input = remaining_input.subspan(1);
221 }
222 {
Brian Silverman0a5e7db2019-02-17 13:45:27 -0800223 uint16_t calculated_crc = jevois_crc_init();
Brian Silverman246cb222019-02-02 16:38:18 -0800224 calculated_crc =
Brian Silverman0a5e7db2019-02-17 13:45:27 -0800225 jevois_crc_update(calculated_crc, transfer.data(),
226 transfer.size() - remaining_input.size());
227 calculated_crc = jevois_crc_finalize(calculated_crc);
Brian Silverman246cb222019-02-02 16:38:18 -0800228 uint16_t received_crc;
Brian Silverman2eb89762019-02-17 15:16:37 -0800229 CHECK_GE(static_cast<size_t>(remaining_input.size()), sizeof(received_crc));
Brian Silverman246cb222019-02-02 16:38:18 -0800230 memcpy(&received_crc, &remaining_input[0], sizeof(received_crc));
231 remaining_input = remaining_input.subspan(sizeof(received_crc));
Brian Silverman2eb89762019-02-17 15:16:37 -0800232 CHECK(remaining_input.empty());
Brian Silverman246cb222019-02-02 16:38:18 -0800233 if (calculated_crc != received_crc) {
234 return tl::nullopt;
235 }
236 }
Brian Silverman246cb222019-02-02 16:38:18 -0800237 return message;
238}
239
Brian Silvermana10b87e2019-02-24 15:20:07 -0800240SpiTransfer SpiPackToTeensy(const RoborioToTeensy &message) {
241 SpiTransfer transfer;
242 gsl::span<char> remaining_space = transfer;
243 for (size_t i = 0; i < message.beacon_brightness.size(); ++i) {
244 remaining_space[0] = message.beacon_brightness[i];
245 remaining_space = remaining_space.subspan(1);
246 }
247 remaining_space[0] = message.light_rings.to_ulong() & 0xFF;
248 remaining_space = remaining_space.subspan(1);
249 {
250 const int64_t realtime_now =
251 message.realtime_now.time_since_epoch().count();
252 memcpy(remaining_space.data(), &realtime_now, sizeof(realtime_now));
253 remaining_space = remaining_space.subspan(sizeof(realtime_now));
254 }
Brian Silvermane9924fd2019-03-02 15:20:42 -0800255 memcpy(remaining_space.data(), &message.camera_command, 1);
256 remaining_space = remaining_space.subspan(1);
Brian Silvermana10b87e2019-02-24 15:20:07 -0800257 {
258 uint16_t crc = jevois_crc_init();
259 crc = jevois_crc_update(crc, transfer.data(),
260 transfer.size() - remaining_space.size());
261 crc = jevois_crc_finalize(crc);
262 CHECK_GE(static_cast<size_t>(remaining_space.size()), sizeof(crc));
263 memcpy(&remaining_space[0], &crc, sizeof(crc));
264 remaining_space = remaining_space.subspan(sizeof(crc));
265 }
266 return transfer;
267}
268
269tl::optional<RoborioToTeensy> SpiUnpackToTeensy(
270 gsl::span<const char, spi_transfer_size()> transfer) {
271 RoborioToTeensy message;
272 gsl::span<const char> remaining_input = transfer;
273 for (size_t i = 0; i < message.beacon_brightness.size(); ++i) {
274 message.beacon_brightness[i] = remaining_input[0];
275 remaining_input = remaining_input.subspan(1);
276 }
277 message.light_rings = remaining_input[0];
278 remaining_input = remaining_input.subspan(1);
279 {
280 int64_t realtime_now;
281 memcpy(&realtime_now, remaining_input.data(), sizeof(realtime_now));
282 message.realtime_now = aos::realtime_clock::time_point(
283 aos::realtime_clock::duration(realtime_now));
284 remaining_input = remaining_input.subspan(sizeof(realtime_now));
285 }
Brian Silvermane9924fd2019-03-02 15:20:42 -0800286 memcpy(&message.camera_command, remaining_input.data(), 1);
287 remaining_input = remaining_input.subspan(1);
Brian Silvermana10b87e2019-02-24 15:20:07 -0800288 {
289 uint16_t calculated_crc = jevois_crc_init();
290 calculated_crc =
291 jevois_crc_update(calculated_crc, transfer.data(),
292 transfer.size() - remaining_input.size());
293 calculated_crc = jevois_crc_finalize(calculated_crc);
294 uint16_t received_crc;
295 CHECK_GE(static_cast<size_t>(remaining_input.size()), sizeof(received_crc));
296 memcpy(&received_crc, &remaining_input[0], sizeof(received_crc));
297 remaining_input = remaining_input.subspan(sizeof(received_crc));
298 if (calculated_crc != received_crc) {
299 return tl::nullopt;
300 }
301 }
302 return message;
303}
304
Brian Silverman246cb222019-02-02 16:38:18 -0800305} // namespace jevois
306} // namespace frc971