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