blob: abc178002b0f0a54a304d745d838ef79f302573e [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"
4#include "third_party/GSL/include/gsl/gsl"
Brian Silverman0a5e7db2019-02-17 13:45:27 -08005#include "y2019/jevois/jevois_crc.h"
Brian Silvermanfdfb3132019-02-24 15:27:27 -08006#ifdef __linux__
7#include "aos/logging/logging.h"
8#else
9#define CHECK(...)
10#define CHECK_GE(...)
11#endif
Brian Silverman246cb222019-02-02 16:38:18 -080012
13// SPI transfer format (6x 8 bit frames):
14// 1. 1-byte brightness for each beacon channel.
15// 2. 1-byte specifying on/off for each light ring.
16// 3. 2-byte CRC
17//
18// SPI transfer format (41x 8 bit frames):
19// 1. Camera frame 0
20// 2. Camera frame 1
21// 3. Camera frame 2
22// 4. 2-byte CRC-16
23// Each camera frame (13x 8 bit frames):
24// 1. Duration for how old the frame is. This is a value received from the
25// camera, added to the time between the first character being received
26// by the MCU to the CS line being asserted. Specifically it's an 8 bit
27// unsigned number of ms.
28// 2. Target 0
29// 3. Target 1
30// 4. Target 2
31// Each target (4x 8 bit frames):
32// 1. 10 bits heading
33// 2. 8 bits distance
34// 3. 6 bits skew
35// 4. 6 bits height
36// 5. 1 bit target valid (a present frame has all-valid targets)
37// 6. 1 bit target present (a present frame can have from 0 to 3
38// targets, depending on how many were found)
39// Note that empty frames are still sent to indicate that the camera is
40// still working even though it doesn't see any targets.
41
42namespace frc971 {
43namespace jevois {
44namespace {
45
46constexpr float heading_min() { return -3; }
47constexpr float heading_max() { return 3; }
48constexpr int heading_bits() { return 10; }
49constexpr int heading_offset() { return 0; }
50void heading_pack(float heading, gsl::span<char> destination) {
51 const auto integer = aos::FloatToIntLinear<heading_bits()>(
52 heading_min(), heading_max(), heading);
53 aos::PackBits<uint32_t, heading_bits(), heading_offset()>(integer,
54 destination);
55}
56float heading_unpack(gsl::span<const char> source) {
57 const auto integer =
58 aos::UnpackBits<uint32_t, heading_bits(), heading_offset()>(source);
59 return aos::IntToFloatLinear<heading_bits()>(heading_min(), heading_max(),
60 integer);
61}
62
63constexpr float distance_min() { return 0; }
64constexpr float distance_max() {
65 // The field is 18.4m diagonally.
66 return 18.4;
67}
68constexpr int distance_bits() { return 8; }
69constexpr int distance_offset() { return heading_offset() + heading_bits(); }
70void distance_pack(float distance, gsl::span<char> destination) {
71 const auto integer = aos::FloatToIntLinear<distance_bits()>(
72 distance_min(), distance_max(), distance);
73 aos::PackBits<uint32_t, distance_bits(), distance_offset()>(integer,
74 destination);
75}
76float distance_unpack(gsl::span<const char> source) {
77 const auto integer =
78 aos::UnpackBits<uint32_t, distance_bits(), distance_offset()>(source);
79 return aos::IntToFloatLinear<distance_bits()>(distance_min(), distance_max(),
80 integer);
81}
82
83constexpr float skew_min() { return -3; }
84constexpr float skew_max() { return 3; }
85constexpr int skew_bits() { return 6; }
86constexpr int skew_offset() { return distance_offset() + distance_bits(); }
87void skew_pack(float skew, gsl::span<char> destination) {
88 const auto integer =
89 aos::FloatToIntLinear<skew_bits()>(skew_min(), skew_max(), skew);
90 aos::PackBits<uint32_t, skew_bits(), skew_offset()>(integer, destination);
91}
92float skew_unpack(gsl::span<const char> source) {
93 const auto integer =
94 aos::UnpackBits<uint32_t, skew_bits(), skew_offset()>(source);
95 return aos::IntToFloatLinear<skew_bits()>(skew_min(), skew_max(), integer);
96}
97
98constexpr float height_min() { return 0; }
99constexpr float height_max() { return 1.5; }
100constexpr int height_bits() { return 6; }
101constexpr int height_offset() { return skew_offset() + skew_bits(); }
102void height_pack(float height, gsl::span<char> destination) {
103 const auto integer =
104 aos::FloatToIntLinear<height_bits()>(height_min(), height_max(), height);
105 aos::PackBits<uint32_t, height_bits(), height_offset()>(integer, destination);
106}
107float height_unpack(gsl::span<const char> source) {
108 const auto integer =
109 aos::UnpackBits<uint32_t, height_bits(), height_offset()>(source);
110 return aos::IntToFloatLinear<height_bits()>(height_min(), height_max(),
111 integer);
112}
113
114constexpr int valid_bits() { return 1; }
115constexpr int valid_offset() { return height_offset() + height_bits(); }
116void valid_pack(bool valid, gsl::span<char> destination) {
117 aos::PackBits<uint32_t, valid_bits(), valid_offset()>(valid, destination);
118}
119bool valid_unpack(gsl::span<const char> source) {
120 return aos::UnpackBits<uint32_t, valid_bits(), valid_offset()>(source);
121}
122
123constexpr int present_bits() { return 1; }
124constexpr int present_offset() { return valid_offset() + valid_bits(); }
125void present_pack(bool present, gsl::span<char> destination) {
126 aos::PackBits<uint32_t, present_bits(), present_offset()>(present,
127 destination);
128}
129bool present_unpack(gsl::span<const char> source) {
130 return aos::UnpackBits<uint32_t, present_bits(), present_offset()>(source);
131}
132
133constexpr int next_offset() { return present_offset() + present_bits(); }
134static_assert(next_offset() <= 32, "Target is too big");
135
136} // namespace
137
138SpiTransfer SpiPackToRoborio(const TeensyToRoborio &message) {
139 SpiTransfer transfer;
140 gsl::span<char> remaining_space = transfer;
141 for (int frame = 0; frame < 3; ++frame) {
142 for (int target = 0; target < 3; ++target) {
143 remaining_space[0] = 0;
144 remaining_space[1] = 0;
145 remaining_space[2] = 0;
146 remaining_space[3] = 0;
147
148 if (static_cast<int>(message.frames.size()) > frame) {
149 valid_pack(true, remaining_space);
150 if (static_cast<int>(message.frames[frame].targets.size()) > target) {
151 heading_pack(message.frames[frame].targets[target].heading,
152 remaining_space);
153 distance_pack(message.frames[frame].targets[target].distance,
154 remaining_space);
155 skew_pack(message.frames[frame].targets[target].skew,
156 remaining_space);
157 height_pack(message.frames[frame].targets[target].height,
158 remaining_space);
159 present_pack(true, remaining_space);
160 } else {
161 present_pack(false, remaining_space);
162 }
163 } else {
164 valid_pack(false, remaining_space);
165 }
166
167 remaining_space = remaining_space.subspan(4);
168 }
169 if (static_cast<int>(message.frames.size()) > frame) {
170 const uint8_t age_count = message.frames[frame].age.count();
171 memcpy(&remaining_space[0], &age_count, 1);
172 } else {
173 remaining_space[0] = 0;
174 }
175 remaining_space = remaining_space.subspan(1);
176 }
177 {
Brian Silverman0a5e7db2019-02-17 13:45:27 -0800178 uint16_t crc = jevois_crc_init();
179 crc = jevois_crc_update(crc, transfer.data(),
180 transfer.size() - remaining_space.size());
181 crc = jevois_crc_finalize(crc);
Brian Silverman2eb89762019-02-17 15:16:37 -0800182 CHECK_GE(static_cast<size_t>(remaining_space.size()), sizeof(crc));
Brian Silverman246cb222019-02-02 16:38:18 -0800183 memcpy(&remaining_space[0], &crc, sizeof(crc));
184 remaining_space = remaining_space.subspan(sizeof(crc));
185 }
Brian Silverman2eb89762019-02-17 15:16:37 -0800186 CHECK(remaining_space.empty());
Brian Silverman246cb222019-02-02 16:38:18 -0800187 return transfer;
188}
189
190tl::optional<TeensyToRoborio> SpiUnpackToRoborio(const SpiTransfer &transfer) {
191 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
240} // namespace jevois
241} // namespace frc971