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