ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
hmmd.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file hmmd.h
6 * @brief Waveshare HMMD 24 GHz mmWave human micro-motion radar codec (PROTOCORE_ENABLE_HMMD).
7 *
8 * The HMMD (Waveshare's FMCW micro-motion detection module, built on the S3KM1110 / SXKMxxx0 class
9 * of radar SoC) reports human presence and range over a 115200-baud UART, and additionally drives a
10 * bare GPIO **OUT** pin. It is a close relative of the HLK-LD2410 (`services/ld2410`) and shares its
11 * framing exactly - two frame kinds, the same magic sequences, a little-endian intra-frame length:
12 *
13 * @code
14 * report: F4 F3 F2 F1 | len(2) | detect(1) | distance(2) | gate_energy[16](2 each) | F8 F7 F6 F5
15 * command: FD FC FB FA | len(2) | word(2) | [value] | 04 03 02 01
16 * @endcode
17 *
18 * A report's intra-frame length is @ref PROTOCORE_HMMD_REPORT_LEN (1 + 2 + 16*2 = 35), so a whole report
19 * frame is 4 + 2 + 35 + 4 = @ref PROTOCORE_HMMD_FRAME_MAX octets. Everything multi-octet is LITTLE-endian.
20 * Unlike the LD2410 the payload carries no head/tail marker or check byte - the header, footer, and
21 * the length agreeing with the buffer are the whole of the validation.
22 *
23 * Where the LD2410 reports a moving/stationary split with 9 range gates, the HMMD reports a single
24 * detection flag, one distance, and the per-gate energy of 16 gates - it is a micro-motion detector,
25 * so "still person breathing" is the case it is built to catch.
26 *
27 * This codec is pure and host-tested: ::protocore_hmmd_parse_report decodes one report frame and
28 * ::HmmdStream reassembles frames byte-by-byte from a UART with resync on noise (no heap, fixed
29 * buffer), mirroring `Ld2410Stream`. The command encoders build the config frames, and
30 * ::protocore_hmmd_parse_ack decodes the module's replies.
31 *
32 * The module's GPIO OUT pin is a bare active-high presence line with no protocol at all. Feed it to
33 * @ref PresenceCore from `services/rcwl0516` (the shared one-GPIO presence facade) to get the same
34 * debounced, hold-extended presence the RCWL-0516 gets; that is an application-level wiring choice,
35 * so this service deliberately does not depend on that one.
36 *
37 * Framing, the report payload layout, the command words, and the open/close command-mode encoding
38 * were taken from the public `2Grey/s3km1110` reference library and cross-checked for internal
39 * consistency (its `kMaxFrameLength` of 45 and `kDistanceGateCount` of 16 agree exactly with the
40 * 35-octet report payload derived here). No vendor SDK is used or required.
41 *
42 * @author Douglas Quigg (dstroy0)
43 * @date 2026
44 */
45
46#ifndef PROTOCORE_HMMD_H
47#define PROTOCORE_HMMD_H
48
49#include "protocore_config.h" // the entry point: protocore_types.h for the widths
50
51#if PROTOCORE_ENABLE_HMMD
52
54
55// PROTOCORE_HMMD_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
56// it into its arena. A caller takes them once and passes the pointer to every call. How they
57// are carved is this module's and is never named here.
58
59#define PROTOCORE_HMMD_GATES 16
60
61#define PROTOCORE_HMMD_REPORT_LEN 35
62
63#define PROTOCORE_HMMD_FRAME_MAX 45
64
65/** @brief A decoded HMMD target report. */
66typedef struct
67{
68 uint8_t detected; ///< 1 if a target is present.
69 uint16_t distance_cm; ///< target distance (cm); meaningless unless detected.
70 uint16_t gate_energy[PROTOCORE_HMMD_GATES]; ///< per-gate energy, gate 0..15.
71} HmmdReport;
72
73/** @brief Byte-by-byte report-frame reassembler (fixed buffer, resyncs on noise). */
74typedef struct
75{
76 uint8_t buf[PROTOCORE_HMMD_FRAME_MAX]; ///< frame under construction
77 uint16_t pos; ///< octets collected so far
78 uint16_t total; ///< expected full-frame length (known after the length field)
79 uint8_t hdr_match; ///< header octets matched while syncing
80 uint8_t phase; ///< 0 sync, 1 length, 2 body
81} HmmdStream;
82
83/** @brief A decoded command-ACK frame. @ref payload points into the caller's frame (not copied). */
84typedef struct
85{
86 uint16_t command; ///< the ACK's command word, as sent on the wire.
87 const uint8_t *payload; ///< octets following the command word (nullptr if none).
88 size_t payload_len; ///< octets at @ref payload.
89} HmmdAck;
90
91/** @brief What parse_report takes: frame, len, out. */
92typedef struct
93{
94 const uint8_t *frame;
95 size_t len;
96 HmmdReport *out;
97} HmmdParseReportArgs;
98
99/** @brief What stream_reset takes: s. */
100typedef struct
101{
102 HmmdStream *s;
103} HmmdStreamResetArgs;
104
105/** @brief What stream_push takes: s, byte, out. */
106typedef struct
107{
108 HmmdStream *s;
109 uint8_t byte;
110 HmmdReport *out;
111} HmmdStreamPushArgs;
112
113/** @brief What present takes: r. */
114typedef struct
115{
116 const HmmdReport *r;
117} HmmdPresentArgs;
118
119/** @brief What distance_cm takes: r. */
120typedef struct
121{
122 const HmmdReport *r;
123} HmmdDistanceCmArgs;
124
125/** @brief What cmd_build takes: buf, cap, word, value, vlen. */
126typedef struct
127{
128 uint8_t *buf;
129 size_t cap;
130 uint16_t word;
131 const uint8_t *value;
132 size_t vlen;
133} HmmdCmdBuildArgs;
134
135/** @brief What cmd_open takes: buf, cap. */
136typedef struct
137{
138 uint8_t *buf;
139 size_t cap;
140} HmmdCmdOpenArgs;
141
142/** @brief What cmd_close takes: buf, cap. */
143typedef struct
144{
145 uint8_t *buf;
146 size_t cap;
147} HmmdCmdCloseArgs;
148
149/** @brief What cmd_read_firmware takes: buf, cap. */
150typedef struct
151{
152 uint8_t *buf;
153 size_t cap;
154} HmmdCmdReadFirmwareArgs;
155
156/** @brief What cmd_read_serial takes: buf, cap. */
157typedef struct
158{
159 uint8_t *buf;
160 size_t cap;
161} HmmdCmdReadSerialArgs;
162
163/** @brief What cmd_read_config takes: buf, cap. */
164typedef struct
165{
166 uint8_t *buf;
167 size_t cap;
168} HmmdCmdReadConfigArgs;
169
170/** @brief What cmd_read_register takes: buf, cap, value, vlen. */
171typedef struct
172{
173 uint8_t *buf;
174 size_t cap;
175 const uint8_t *value;
176 size_t vlen;
177} HmmdCmdReadRegisterArgs;
178
179/** @brief What parse_ack takes: frame, len, out. */
180typedef struct
181{
182 const uint8_t *frame;
183 size_t len;
184 HmmdAck *out;
185} HmmdParseAckArgs;
186
187/** @brief What ack_matches takes: ack, word. */
188typedef struct
189{
190 const HmmdAck *ack;
191 uint16_t word;
192} HmmdAckMatchesArgs;
193
194/** @brief What begin takes: rx_pin, tx_pin. */
195typedef struct
196{
197 int rx_pin;
198 int tx_pin;
199} HmmdBeginArgs;
200
201/**
202 * @brief Waveshare HMMD 24 GHz mmWave human micro-motion radar codec (PROTOCORE_ENABLE_HMMD). The HMMD (Waveshare's ...
203 *
204 * A caller sets the members a call takes, invokes it through ::Hmmd with the bytes it runs
205 * out of, and reads the outcome off the same handle.
206 *
207 * Hmmd.parse_report_args.frame = ...;
208 * Hmmd.parse_report_args.len = ...;
209 * Hmmd.parse_report_args.out = ...;
210 * Hmmd.parse_report(work);
211 * // Hmmd.ok is what the call reports
212 *
213 * @var HmmdNs::parse_report_args what parse_report takes: frame, len, out
214 * @var HmmdNs::stream_reset_args what stream_reset takes: s
215 * @var HmmdNs::stream_push_args what stream_push takes: s, byte, out
216 * @var HmmdNs::present_args what present takes: r
217 * @var HmmdNs::distance_cm_args what distance_cm takes: r
218 * @var HmmdNs::cmd_build_args what cmd_build takes: buf, cap, word, value, vlen
219 * @var HmmdNs::cmd_open_args what cmd_open takes: buf, cap
220 * @var HmmdNs::cmd_close_args what cmd_close takes: buf, cap
221 * @var HmmdNs::cmd_read_firmware_args what cmd_read_firmware takes: buf, cap
222 * @var HmmdNs::cmd_read_serial_args what cmd_read_serial takes: buf, cap
223 * @var HmmdNs::cmd_read_config_args what cmd_read_config takes: buf, cap
224 * @var HmmdNs::cmd_read_register_args what cmd_read_register takes: buf, cap, value, vlen
225 * @var HmmdNs::parse_ack_args what parse_ack takes: frame, len, out
226 * @var HmmdNs::ack_matches_args what ack_matches takes: ack, word
227 * @var HmmdNs::begin_args what begin takes: rx_pin, tx_pin
228 * @var HmmdNs::ok true on a valid frame; false on any mismatch or a short buffer
229 * @var HmmdNs::cm what a call reports
230 * @var HmmdNs::n the count a call reports
231 * @var HmmdNs::report what a call reports
232 * @var HmmdNs::parse_report decode one whole HMMD report frame (header `F4 F3 F2 F1` .. footer ...
233 * @var HmmdNs::stream_reset reset a stream to the syncing state
234 * @var HmmdNs::stream_push feed one received octet. When it completes a valid report frame, ...
235 * @var HmmdNs::present true if r shows a target
236 * @var HmmdNs::distance_cm target distance (cm), or 0 when nothing is detected
237 * @var HmmdNs::cmd_build build an arbitrary command frame: word plus vlen octets of value. ...
238 * @var HmmdNs::cmd_open "Open command mode" (word 0x00FF, value 0x0001)
239 * @var HmmdNs::cmd_close "Close command mode" (word 0x00FE, no value)
240 * @var HmmdNs::cmd_read_firmware read the radar firmware version (word 0x0000)
241 * @var HmmdNs::cmd_read_serial read the module serial number (word 0x0011)
242 * @var HmmdNs::cmd_read_config read the parameter configuration (word 0x0008)
243 * @var HmmdNs::cmd_read_register read a register (word 0x0002), with vlen octets of caller-supplied ...
244 * @var HmmdNs::parse_ack decode one command-ACK frame (header, intra-frame length, footer ...
245 * @var HmmdNs::ack_matches true if ack is the reply to request word. Matches on the low octet, ...
246 * @var HmmdNs::begin open PROTOCORE_HMMD_UART at PROTOCORE_HMMD_BAUD on rx_pin / tx_pin. ...
247 * @var HmmdNs::poll pump the UART through the stream. true if a fresh report was decoded
248 * @var HmmdNs::last the most recently decoded report, or NULL before the first one ...
249 *
250 * @c work is PROTOCORE_HMMD_BORROW bytes the CALLER took, at an address it knows. It is not held past the call, so
251 * nothing here aliases it. How those bytes are carved is this module's and is never named here.
252 */
253typedef struct
254{
255 HmmdParseReportArgs parse_report_args;
256 HmmdStreamResetArgs stream_reset_args;
257 HmmdStreamPushArgs stream_push_args;
258 HmmdPresentArgs present_args;
259 HmmdDistanceCmArgs distance_cm_args;
260 HmmdCmdBuildArgs cmd_build_args;
261 HmmdCmdOpenArgs cmd_open_args;
262 HmmdCmdCloseArgs cmd_close_args;
263 HmmdCmdReadFirmwareArgs cmd_read_firmware_args;
264 HmmdCmdReadSerialArgs cmd_read_serial_args;
265 HmmdCmdReadConfigArgs cmd_read_config_args;
266 HmmdCmdReadRegisterArgs cmd_read_register_args;
267 HmmdParseAckArgs parse_ack_args;
268 HmmdAckMatchesArgs ack_matches_args;
269 HmmdBeginArgs begin_args;
270
271 proto_bool ok;
272 uint16_t cm;
273 size_t n;
274 const HmmdReport *report;
275
276 void (*const parse_report)(uint8_t *work);
277 void (*const stream_reset)(uint8_t *work);
278 void (*const stream_push)(uint8_t *work);
279 void (*const present)(uint8_t *work);
280 void (*const distance_cm)(uint8_t *work);
281 void (*const cmd_build)(uint8_t *work);
282 void (*const cmd_open)(uint8_t *work);
283 void (*const cmd_close)(uint8_t *work);
284 void (*const cmd_read_firmware)(uint8_t *work);
285 void (*const cmd_read_serial)(uint8_t *work);
286 void (*const cmd_read_config)(uint8_t *work);
287 void (*const cmd_read_register)(uint8_t *work);
288 void (*const parse_ack)(uint8_t *work);
289 void (*const ack_matches)(uint8_t *work);
290 void (*const begin)(uint8_t *work);
291 void (*const poll)(uint8_t *work);
292 void (*const last)(uint8_t *work);
293} HmmdNs;
294
295/** @brief The one symbol this module exports. */
296extern HmmdNs Hmmd;
297
298/**
299 * @brief The PROTOCORE_HMMD_BORROW bytes this module's state lives in.
300 *
301 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
302 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
303 * walks, so the state lasts the life of the program.
304 *
305 * @return the span.
306 */
307uint8_t *protocore_hmmd_span(void);
308
310
311#endif // PROTOCORE_ENABLE_HMMD
312
313#endif // PROTOCORE_HMMD_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97