ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
hmmd.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file hmmd.cpp
6 * @brief Waveshare HMMD mmWave radar codec - implementation. See hmmd.h.
7 *
8 * Frame layout (little-endian throughout), per the S3KM1110 serial protocol:
9 * report: F4 F3 F2 F1 | len(2)=35 | detect(1) | dist(2) | gate[16](2 each) | F8 F7 F6 F5
10 * command: FD FC FB FA | len(2) | word(2) | [value] | 04 03 02 01
11 */
12
14
15#if PC_ENABLE_HMMD
16
17#include <string.h>
18
19#if defined(ARDUINO)
20#include <Arduino.h>
21#endif
22namespace
23{
24const uint8_t HDR[4] = {0xF4, 0xF3, 0xF2, 0xF1};
25const uint8_t FTR[4] = {0xF8, 0xF7, 0xF6, 0xF5};
26const uint8_t CMD_HDR[4] = {0xFD, 0xFC, 0xFB, 0xFA};
27const uint8_t CMD_FTR[4] = {0x04, 0x03, 0x02, 0x01};
28
29uint16_t rd16(const uint8_t *p)
30{
31 return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
32}
33} // namespace
34
35bool pc_hmmd_parse_report(const uint8_t *f, size_t len, HmmdReport *out)
36{
37 if (!f || !out || len != PC_HMMD_FRAME_MAX)
38 {
39 return false;
40 }
41 if (memcmp(f, HDR, 4) != 0)
42 {
43 return false;
44 }
45 if (rd16(f + 4) != PC_HMMD_REPORT_LEN)
46 {
47 return false; // the only report length this module emits
48 }
49 if (memcmp(f + 6 + PC_HMMD_REPORT_LEN, FTR, 4) != 0)
50 {
51 return false;
52 }
53
54 const uint8_t *p = f + 6;
55 HmmdReport r;
56 memset(&r, 0, sizeof(r));
57 r.detected = (p[0] == 0x01) ? 1u : 0u;
58 r.distance_cm = rd16(p + 1);
59 for (int i = 0; i < PC_HMMD_GATES; i++)
60 {
61 r.gate_energy[i] = rd16(p + 3 + 2 * i);
62 }
63 *out = r;
64 return true;
65}
66
67void pc_hmmd_stream_reset(HmmdStream *s)
68{
69 if (!s)
70 {
71 return;
72 }
73 s->pos = 0;
74 s->total = 0;
75 s->hdr_match = 0;
76 s->phase = 0;
77}
78
79bool pc_hmmd_stream_push(HmmdStream *s, uint8_t b, HmmdReport *out)
80{
81 if (!s || !out)
82 {
83 return false;
84 }
85 switch (s->phase)
86 {
87 case 0: // sync on the 4-octet header (its octets are distinct, so a resync restarts at most at [0])
88 if (b == HDR[s->hdr_match])
89 {
90 s->buf[s->hdr_match++] = b;
91 if (s->hdr_match == 4)
92 {
93 s->pos = 4;
94 s->phase = 1;
95 }
96 }
97 else
98 {
99 s->hdr_match = (b == HDR[0]) ? 1 : 0;
100 if (s->hdr_match)
101 {
102 s->buf[0] = b;
103 }
104 }
105 return false;
106 case 1: // little-endian length field
107 s->buf[s->pos++] = b;
108 if (s->pos == 6)
109 {
110 // Widen before comparing: 6 + 0xFFFF + 4 wraps a uint16_t, defeating the guard.
111 uint32_t total = 6u + (uint32_t)rd16(s->buf + 4) + 4u;
112 if (total > PC_HMMD_FRAME_MAX)
113 {
114 pc_hmmd_stream_reset(s); // absurd length: drop and resync
115 return false;
116 }
117 s->total = (uint16_t)total;
118 s->phase = 2;
119 }
120 return false;
121 default: // body + footer
122 s->buf[s->pos++] = b;
123 if (s->pos >= s->total)
124 {
125 bool ok = pc_hmmd_parse_report(s->buf, s->total, out);
126 pc_hmmd_stream_reset(s);
127 return ok;
128 }
129 return false;
130 }
131}
132
133bool pc_hmmd_present(const HmmdReport *r)
134{
135 return r && r->detected != 0;
136}
137
138uint16_t pc_hmmd_distance_cm(const HmmdReport *r)
139{
140 return (r && r->detected) ? r->distance_cm : 0;
141}
142
143// --- command encoders ------------------------------------------------------
144
145size_t pc_hmmd_cmd_build(uint8_t *buf, size_t cap, uint16_t word, const uint8_t *value, size_t vlen)
146{
147 if (vlen && !value)
148 {
149 return 0;
150 }
151 size_t need = 4 + 2 + 2 + vlen + 4;
152 if (!buf || cap < need)
153 {
154 return 0;
155 }
156 size_t i = 0;
157 for (int k = 0; k < 4; k++)
158 {
159 buf[i++] = CMD_HDR[k];
160 }
161 uint16_t dl = (uint16_t)(2 + vlen); // command word + value; header/footer excluded
162 buf[i++] = (uint8_t)(dl & 0xFF);
163 buf[i++] = (uint8_t)(dl >> 8);
164 buf[i++] = (uint8_t)(word & 0xFF);
165 buf[i++] = (uint8_t)(word >> 8);
166 for (size_t k = 0; k < vlen; k++)
167 {
168 buf[i++] = value[k];
169 }
170 for (int k = 0; k < 4; k++)
171 {
172 buf[i++] = CMD_FTR[k];
173 }
174 return i;
175}
176
177size_t pc_hmmd_cmd_open(uint8_t *buf, size_t cap)
178{
179 static const uint8_t v[2] = {0x01, 0x00}; // value 0x0001
180 return pc_hmmd_cmd_build(buf, cap, 0x00FF, v, 2);
181}
182
183size_t pc_hmmd_cmd_close(uint8_t *buf, size_t cap)
184{
185 return pc_hmmd_cmd_build(buf, cap, 0x00FE, nullptr, 0);
186}
187
188size_t pc_hmmd_cmd_read_firmware(uint8_t *buf, size_t cap)
189{
190 return pc_hmmd_cmd_build(buf, cap, 0x0000, nullptr, 0);
191}
192
193size_t pc_hmmd_cmd_read_serial(uint8_t *buf, size_t cap)
194{
195 return pc_hmmd_cmd_build(buf, cap, 0x0011, nullptr, 0);
196}
197
198size_t pc_hmmd_cmd_read_config(uint8_t *buf, size_t cap)
199{
200 return pc_hmmd_cmd_build(buf, cap, 0x0008, nullptr, 0);
201}
202
203size_t pc_hmmd_cmd_read_register(uint8_t *buf, size_t cap, const uint8_t *value, size_t vlen)
204{
205 return pc_hmmd_cmd_build(buf, cap, 0x0002, value, vlen);
206}
207
208// --- command-ACK decoding --------------------------------------------------
209
210bool pc_hmmd_parse_ack(const uint8_t *f, size_t len, HmmdAck *out)
211{
212 // layout: four header bytes, two length bytes, two command-word bytes, optional data, four footer bytes
213 if (!f || !out || len < 12)
214 {
215 return false;
216 }
217 if (memcmp(f, CMD_HDR, 4) != 0)
218 {
219 return false;
220 }
221 size_t dl = (size_t)rd16(f + 4); // command word + data
222 if (dl < 2 || len != 4 + 2 + dl + 4)
223 {
224 return false; // the declared length must account for exactly this frame
225 }
226 if (memcmp(f + 6 + dl, CMD_FTR, 4) != 0)
227 {
228 return false;
229 }
230 out->command = rd16(f + 6);
231 out->payload_len = dl - 2;
232 out->payload = out->payload_len ? f + 8 : nullptr;
233 return true;
234}
235
236bool pc_hmmd_ack_matches(const HmmdAck *ack, uint16_t word)
237{
238 return ack && (uint8_t)(ack->command & 0xFF) == (uint8_t)(word & 0xFF);
239}
240
241// ---------------------------------------------------------------------------
242// UART binding
243// ---------------------------------------------------------------------------
244
245#if defined(ARDUINO)
246
247namespace
248{
249// All HMMD UART-binding state, owned by one instance (internal linkage): the frame reassembler, the
250// last decoded report, and the have-report flag, grouped so it is one named owner unreachable from
251// any other translation unit.
252struct HmmdCtx
253{
254 HmmdStream stream;
255 HmmdReport last;
256 bool have = false;
257};
258HmmdCtx s_hmmd;
259} // namespace
260
261bool pc_hmmd_begin(int rx_pin, int tx_pin)
262{
263 pc_hmmd_stream_reset(&s_hmmd.stream);
264 s_hmmd.have = false;
265 Serial2.begin(PC_HMMD_BAUD, SERIAL_8N1, rx_pin, tx_pin);
266 return true;
267}
268
269bool pc_hmmd_poll()
270{
271 bool fresh = false;
272 while (Serial2.available())
273 {
274 HmmdReport r;
275 if (pc_hmmd_stream_push(&s_hmmd.stream, (uint8_t)Serial2.read(), &r))
276 {
277 s_hmmd.last = r;
278 s_hmmd.have = true;
279 fresh = true;
280 }
281 }
282 return fresh;
283}
284
285const HmmdReport *pc_hmmd_last()
286{
287 return s_hmmd.have ? &s_hmmd.last : nullptr;
288}
289
290#else // host build: no UART
291
292bool pc_hmmd_begin(int, int)
293{
294 return false;
295}
296
297bool pc_hmmd_poll()
298{
299 return false;
300}
301
302const HmmdReport *pc_hmmd_last()
303{
304 return nullptr;
305}
306
307#endif // ARDUINO
308
309#endif // PC_ENABLE_HMMD
Waveshare HMMD 24 GHz mmWave human micro-motion radar codec (PC_ENABLE_HMMD).
#define PC_HMMD_BAUD
HMMD UART baud rate (the module's factory default is 115200).