ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
ld2410.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 ld2410.h
6 * @brief HLK-LD2410 24 GHz mmWave presence / motion radar codec (PROTOCORE_ENABLE_LD2410).
7 *
8 * The LD2410 streams a framed serial report at 256000 baud: header `F4 F3 F2 F1`, a
9 * little-endian intra-frame length, the payload, and footer `F8 F7 F6 F5`. The payload carries
10 * the target state (none / moving / stationary / both), the moving and stationary target
11 * distance (cm) and energy (0-100), the overall detection distance, and - in "engineering
12 * mode" - the per-gate energy of all nine range gates. Configuration is a second frame kind
13 * (header `FD FC FB FA`, footer `04 03 02 01`) carrying a 2-byte command word.
14 *
15 * This codec is pure and host-tested: ::protocore_ld2410_parse_report decodes one report frame, and
16 * ::Ld2410Stream reassembles frames byte-by-byte from a UART with resync on noise (no heap,
17 * fixed buffer). The command encoders build the config frames. Where a bus seam exists the binding
18 * pumps a UART through uart.h and keeps the latest report; only that read/write reaches the seam.
19 *
20 * A cheap solder-and-bench-test breakout: wire it to a UART, wave a hand, watch presence.
21 *
22 * @author Douglas Quigg (dstroy0)
23 * @date 2026
24 */
25
26#ifndef PROTOCORE_LD2410_H
27#define PROTOCORE_LD2410_H
28
29#include "protocore_config.h" // the entry point: protocore_types.h for the widths
30
31#if PROTOCORE_ENABLE_LD2410
32
34
35// PROTOCORE_LD2410_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
36// it into its arena. A caller takes them once and passes the pointer to every call. How they
37// are carved is this module's and is never named here.
38
39#define LD2410_MAX_GATES 9
40
41#define LD2410_FRAME_MAX 72
42
43#define LD2410_STATE_NONE 0x00 ///< no target
44
45#define LD2410_STATE_MOVING 0x01 ///< moving target only
46
47#define LD2410_STATE_STATIC 0x02 ///< stationary target only
48
49#define LD2410_STATE_BOTH 0x03 ///< both a moving and a stationary target
50
51/** @brief A decoded LD2410 target report. Engineering fields are 0 unless @ref engineering. */
52typedef struct
53{
54 uint8_t engineering; ///< 1 if this was an engineering-mode frame (per-gate energies valid)
55 uint8_t state; ///< one of LD2410_STATE_*
56 uint16_t moving_cm; ///< moving target distance (cm)
57 uint8_t moving_energy; ///< moving target energy (0-100)
58 uint16_t static_cm; ///< stationary target distance (cm)
59 uint8_t static_energy; ///< stationary target energy (0-100)
60 uint16_t detect_cm; ///< overall detection distance (cm)
61 // Engineering mode only:
62 uint8_t max_moving_gate; ///< highest configured moving gate
63 uint8_t max_static_gate; ///< highest configured stationary gate
64 uint8_t moving_gate_energy[LD2410_MAX_GATES]; ///< per-gate moving energy (0-100)
65 uint8_t static_gate_energy[LD2410_MAX_GATES]; ///< per-gate stationary energy (0-100)
66 uint8_t light; ///< photosensor level (0-255)
67 uint8_t out_pin; ///< OUT pin level (0/1)
68} Ld2410Report;
69
70/** @brief Byte-by-byte report-frame reassembler (fixed buffer, resyncs on noise). */
71typedef struct
72{
73 uint8_t buf[LD2410_FRAME_MAX]; ///< frame under construction
74 uint16_t pos; ///< bytes collected so far
75 uint16_t total; ///< expected full-frame length (known after the length field)
76 uint8_t hdr_match; ///< header bytes matched while syncing (phase = pos<4)
77 uint8_t phase; ///< 0 sync, 1 length, 2 body
78} Ld2410Stream;
79
80/** @brief A decoded command-ACK frame. @ref payload points into the caller's frame (not copied). */
81typedef struct
82{
83 uint16_t command; ///< ACK command word: the request word | 0x0100 (e.g. 0x01A5 for get-MAC).
84 uint16_t status; ///< 0 = success, 1 = failure.
85 const uint8_t *payload; ///< command-specific data after the status word (nullptr if none).
86 size_t payload_len; ///< octets at @ref payload.
87} Ld2410Ack;
88
89/** @brief What parse_report takes: frame, len, out. */
90typedef struct
91{
92 const uint8_t *frame;
93 size_t len;
94 Ld2410Report *out;
95} Ld2410ParseReportArgs;
96
97/** @brief What stream_reset takes: s. */
98typedef struct
99{
100 Ld2410Stream *s;
101} Ld2410StreamResetArgs;
102
103/** @brief What stream_push takes: s, byte, out. */
104typedef struct
105{
106 Ld2410Stream *s;
107 uint8_t byte;
108 Ld2410Report *out;
109} Ld2410StreamPushArgs;
110
111/** @brief What present takes: r. */
112typedef struct
113{
114 const Ld2410Report *r;
115} Ld2410PresentArgs;
116
117/** @brief What distance_cm takes: r. */
118typedef struct
119{
120 const Ld2410Report *r;
121} Ld2410DistanceCmArgs;
122
123/** @brief What cmd_config_enable takes: buf, cap. */
124typedef struct
125{
126 uint8_t *buf;
127 size_t cap;
128} Ld2410CmdConfigEnableArgs;
129
130/** @brief What cmd_config_end takes: buf, cap. */
131typedef struct
132{
133 uint8_t *buf;
134 size_t cap;
135} Ld2410CmdConfigEndArgs;
136
137/** @brief What cmd_engineering takes: buf, cap, on. */
138typedef struct
139{
140 uint8_t *buf;
141 size_t cap;
142 proto_bool on;
143} Ld2410CmdEngineeringArgs;
144
145/** @brief What cmd_restart takes: buf, cap. */
146typedef struct
147{
148 uint8_t *buf;
149 size_t cap;
150} Ld2410CmdRestartArgs;
151
152/** @brief What cmd_bluetooth takes: buf, cap, on. */
153typedef struct
154{
155 uint8_t *buf;
156 size_t cap;
157 proto_bool on;
158} Ld2410CmdBluetoothArgs;
159
160/** @brief What cmd_get_mac takes: buf, cap. */
161typedef struct
162{
163 uint8_t *buf;
164 size_t cap;
165} Ld2410CmdGetMacArgs;
166
167/** @brief What cmd_set_bt_password takes: buf, cap, password. */
168typedef struct
169{
170 uint8_t *buf;
171 size_t cap;
172 const char *password; ///< 6 bytes.
173} Ld2410CmdSetBtPasswordArgs;
174
175/** @brief What parse_ack takes: frame, len, out. */
176typedef struct
177{
178 const uint8_t *frame;
179 size_t len;
180 Ld2410Ack *out;
181} Ld2410ParseAckArgs;
182
183/** @brief What ack_ok takes: ack. */
184typedef struct
185{
186 const Ld2410Ack *ack;
187} Ld2410AckOkArgs;
188
189/** @brief What ack_mac takes: ack, mac. */
190typedef struct
191{
192 const Ld2410Ack *ack;
193 uint8_t *mac; ///< 6 bytes.
194} Ld2410AckMacArgs;
195
196/** @brief What begin takes: rx_pin, tx_pin. */
197typedef struct
198{
199 int rx_pin;
200 int tx_pin;
201} Ld2410BeginArgs;
202
203/** @brief What set_engineering takes: on. */
204typedef struct
205{
206 proto_bool on;
207} Ld2410SetEngineeringArgs;
208
209/**
210 * @brief HLK-LD2410 24 GHz mmWave presence / motion radar codec (PROTOCORE_ENABLE_LD2410). The LD2410 streams a framed
211 * ...
212 *
213 * A caller sets the members a call takes, invokes it through ::Ld2410 with the bytes it runs
214 * out of, and reads the outcome off the same handle.
215 *
216 * Ld2410.parse_report_args.frame = ...;
217 * Ld2410.parse_report_args.len = ...;
218 * Ld2410.parse_report_args.out = ...;
219 * Ld2410.parse_report(work);
220 * // Ld2410.ok is what the call reports
221 *
222 * @var Ld2410Ns::parse_report_args what parse_report takes: frame, len, out
223 * @var Ld2410Ns::stream_reset_args what stream_reset takes: s
224 * @var Ld2410Ns::stream_push_args what stream_push takes: s, byte, out
225 * @var Ld2410Ns::present_args what present takes: r
226 * @var Ld2410Ns::distance_cm_args what distance_cm takes: r
227 * @var Ld2410Ns::cmd_config_enable_args what cmd_config_enable takes: buf, cap
228 * @var Ld2410Ns::cmd_config_end_args what cmd_config_end takes: buf, cap
229 * @var Ld2410Ns::cmd_engineering_args what cmd_engineering takes: buf, cap, on
230 * @var Ld2410Ns::cmd_restart_args what cmd_restart takes: buf, cap
231 * @var Ld2410Ns::cmd_bluetooth_args what cmd_bluetooth takes: buf, cap, on
232 * @var Ld2410Ns::cmd_get_mac_args what cmd_get_mac takes: buf, cap
233 * @var Ld2410Ns::cmd_set_bt_password_args what cmd_set_bt_password takes: buf, cap, password
234 * @var Ld2410Ns::parse_ack_args what parse_ack takes: frame, len, out
235 * @var Ld2410Ns::ack_ok_args what ack_ok takes: ack
236 * @var Ld2410Ns::ack_mac_args what ack_mac takes: ack, mac
237 * @var Ld2410Ns::begin_args what begin takes: rx_pin, tx_pin
238 * @var Ld2410Ns::set_engineering_args what set_engineering takes: on
239 * @var Ld2410Ns::ok true on a valid frame; false on any mismatch or a short buffer
240 * @var Ld2410Ns::cm what a call reports
241 * @var Ld2410Ns::n the count a call reports
242 * @var Ld2410Ns::report what a call reports
243 * @var Ld2410Ns::parse_report decode one whole LD2410 report frame (header `F4 F3 F2 F1` .. ...
244 * @var Ld2410Ns::stream_reset reset a stream to the syncing state
245 * @var Ld2410Ns::stream_push feed one received byte. When it completes a valid report frame, ...
246 * @var Ld2410Ns::present true if r shows any target (moving or stationary)
247 * @var Ld2410Ns::distance_cm best available target distance (cm): the moving distance if moving, ...
248 * @var Ld2410Ns::cmd_config_enable "Enable configuration" (word 0x00FF, value 0x0001)
249 * @var Ld2410Ns::cmd_config_end "End configuration" (word 0x00FE)
250 * @var Ld2410Ns::cmd_engineering enable (0x0062) or disable (0x0063) engineering mode
251 * @var Ld2410Ns::cmd_restart restart the module (word 0x00A3)
252 * @var Ld2410Ns::cmd_bluetooth LD2410B: turn the Bluetooth radio on (value 0x0001) or off ...
253 * @var Ld2410Ns::cmd_get_mac LD2410B: query the module's Bluetooth MAC address (word 0x00A5, ...
254 * @var Ld2410Ns::cmd_set_bt_password LD2410B: set the 6-octet Bluetooth control password (word 0x00A9). ...
255 * @var Ld2410Ns::parse_ack decode one command-ACK frame (header, intra-frame length, footer ...
256 * @var Ld2410Ns::ack_ok true if ack reports success (Ld2410Ack::status == 0)
257 * @var Ld2410Ns::ack_mac extract the 6-octet MAC from a get-MAC ACK (word 0x01A5) into mac, ...
258 * @var Ld2410Ns::begin open PROTOCORE_LD2410_UART at PROTOCORE_LD2410_BAUD on rx_pin / ...
259 * @var Ld2410Ns::poll pump the UART through the stream. true if a fresh report was decoded
260 * @var Ld2410Ns::last the most recently decoded report, or NULL before the first one ...
261 * @var Ld2410Ns::set_engineering enable/disable engineering mode (brackets the command with ...
262 * @var Ld2410Ns::restart restart the module (brackets the command with enable/end)
263 *
264 * @c work is PROTOCORE_LD2410_BORROW bytes the CALLER took, at an address it knows. It is not held past the call, so
265 * nothing here aliases it. How those bytes are carved is this module's and is never named here.
266 */
267typedef struct
268{
269 Ld2410ParseReportArgs parse_report_args;
270 Ld2410StreamResetArgs stream_reset_args;
271 Ld2410StreamPushArgs stream_push_args;
272 Ld2410PresentArgs present_args;
273 Ld2410DistanceCmArgs distance_cm_args;
274 Ld2410CmdConfigEnableArgs cmd_config_enable_args;
275 Ld2410CmdConfigEndArgs cmd_config_end_args;
276 Ld2410CmdEngineeringArgs cmd_engineering_args;
277 Ld2410CmdRestartArgs cmd_restart_args;
278 Ld2410CmdBluetoothArgs cmd_bluetooth_args;
279 Ld2410CmdGetMacArgs cmd_get_mac_args;
280 Ld2410CmdSetBtPasswordArgs cmd_set_bt_password_args;
281 Ld2410ParseAckArgs parse_ack_args;
282 Ld2410AckOkArgs ack_ok_args;
283 Ld2410AckMacArgs ack_mac_args;
284 Ld2410BeginArgs begin_args;
285 Ld2410SetEngineeringArgs set_engineering_args;
286
287 proto_bool ok;
288 uint16_t cm;
289 size_t n;
290 const Ld2410Report *report;
291
292 void (*const parse_report)(uint8_t *work);
293 void (*const stream_reset)(uint8_t *work);
294 void (*const stream_push)(uint8_t *work);
295 void (*const present)(uint8_t *work);
296 void (*const distance_cm)(uint8_t *work);
297 void (*const cmd_config_enable)(uint8_t *work);
298 void (*const cmd_config_end)(uint8_t *work);
299 void (*const cmd_engineering)(uint8_t *work);
300 void (*const cmd_restart)(uint8_t *work);
301 void (*const cmd_bluetooth)(uint8_t *work);
302 void (*const cmd_get_mac)(uint8_t *work);
303 void (*const cmd_set_bt_password)(uint8_t *work);
304 void (*const parse_ack)(uint8_t *work);
305 void (*const ack_ok)(uint8_t *work);
306 void (*const ack_mac)(uint8_t *work);
307 void (*const begin)(uint8_t *work);
308 void (*const poll)(uint8_t *work);
309 void (*const last)(uint8_t *work);
310 void (*const set_engineering)(uint8_t *work);
311 void (*const restart)(uint8_t *work);
312} Ld2410Ns;
313
314/** @brief The one symbol this module exports. */
315extern Ld2410Ns Ld2410;
316
317/**
318 * @brief The PROTOCORE_LD2410_BORROW bytes this module's state lives in.
319 *
320 * Stated beside the namespace rather than on it: an entry takes a borrow, and this is where
321 * that borrow comes from. Taken once from the end of the pool, which no mark and no release
322 * walks, so the state lasts the life of the program.
323 *
324 * @return the span.
325 */
326uint8_t *protocore_ld2410_span(void);
327
329
330#endif // PROTOCORE_ENABLE_LD2410
331
332#endif // PROTOCORE_LD2410_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