1
+ #pragma once
2
+
1
3
#include < stdbool.h>
2
4
#include < stdint.h>
3
5
28
30
29
31
namespace audio_tools {
30
32
31
- enum class HDLCWriteLogic { OnBufferFull, OnFlush, onWrite };
33
+ /* *
34
+ * @enum HDLCWriteLogic
35
+ * @brief Defines when the actual HDLC frame is written to the output stream
36
+ */
37
+ enum class HDLCWriteLogic {
38
+ /* * @brief Write frames when the internal buffer is full */
39
+ OnBufferFull,
40
+ /* * @brief Write frames when flush() is called */
41
+ OnFlush,
42
+ /* * @brief Write frames immediately on each write() call */
43
+ OnWrite
44
+ };
32
45
33
46
/* *
34
47
* @brief High-Level Data Link Control (HDLC) is a bit-oriented code-transparent
35
- * synchronous data link layer protocol in scenarios where you need reliable,
36
- * framed, and error-checked communication between devices at the data link
37
- * layer.
48
+ * synchronous data link layer protocol for reliable, framed, and error-checked communication.
49
+ *
50
+ * This class implements HDLC framing with:
51
+ * - Frame delimiter (0x7E) for marking frame boundaries
52
+ * - Byte stuffing to escape special characters in the data
53
+ * - 16-bit CRC-CCITT for error detection
54
+ * - Transparent Stream interface for easy integration
38
55
*
39
56
* @ingroup communications
40
57
* @author Phil Schatzmann
41
58
*/
42
-
43
59
class HDLCStream : public Stream {
44
60
public:
45
- // / Defines the output for the hdlc encoding
61
+ /* *
62
+ * @brief Constructor with write-only operation
63
+ *
64
+ * @param out Print object to which encoded HDLC frames will be written
65
+ * @param max_frame_length Maximum size of a single HDLC frame in bytes
66
+ */
46
67
HDLCStream (Print &out, uint16_t max_frame_length) {
47
68
setOutput (out);
48
69
this ->max_frame_length = max_frame_length;
49
70
begin ();
50
71
}
51
72
52
- // / Defines the input for the hdlc decoding
73
+ /* *
74
+ * @brief Constructor with read-write operation
75
+ *
76
+ * @param io Stream object for bidirectional HDLC communication
77
+ * @param max_frame_length Maximum size of a single HDLC frame in bytes
78
+ */
53
79
HDLCStream (Stream &io, uint16_t max_frame_length) {
54
80
setStream (io);
55
81
this ->max_frame_length = max_frame_length;
56
82
begin ();
57
83
}
58
84
85
+ /* *
86
+ * @brief Initialize the HDLC stream
87
+ *
88
+ * Resets the frame position, checksum, and escape character state.
89
+ * Allocates the frame buffer to the specified max_frame_length.
90
+ *
91
+ * @return true if a valid input or output stream has been set
92
+ * @return false if no valid stream is available
93
+ */
59
94
bool begin () {
60
95
this ->frame_position = 0 ;
61
96
this ->frame_checksum = CRC16_CCITT_INIT_VAL;
62
97
this ->escape_character = false ;
63
- if (frame_buffer.size () == 0 ) {
64
- frame_buffer.resize (max_frame_length + 1 );
98
+ if (frame_buffer.size () != max_frame_length ) {
99
+ frame_buffer.resize (max_frame_length);
65
100
}
66
101
return p_out != nullptr || p_in != nullptr ;
67
102
}
68
103
104
+ /* *
105
+ * @brief Clean up resources used by the HDLC stream
106
+ *
107
+ * Releases the frame buffer memory by resizing it to zero.
108
+ */
69
109
void end () { this ->frame_buffer .resize (0 ); }
70
110
111
+ /* *
112
+ * @brief Get the number of bytes available in the write buffer
113
+ *
114
+ * @return int Available space for writing or 0 if no output is defined
115
+ */
71
116
int availableForWrite () override {
72
117
return p_out == nullptr ? 0 : DEFAULT_BUFFER_SIZE;
73
118
}
74
119
75
- // / Sends the encoded data to the defined output
120
+ /* *
121
+ * @brief Write data to be encoded as HDLC frames
122
+ *
123
+ * Processes data according to the current HDLCWriteLogic setting:
124
+ * - OnBufferFull: Stores data in buffer, sends when buffer is full
125
+ * - OnFlush: Stores data in buffer, waits for flush() to send
126
+ * - OnWrite: Immediately sends data as an HDLC frame
127
+ *
128
+ * @param data Pointer to the data buffer to be written
129
+ * @param len Number of bytes to write
130
+ * @return size_t Number of bytes accepted (typically equals len)
131
+ */
76
132
size_t write (const uint8_t *data, size_t len) override {
77
133
LOGD (" HDLCStream::write: %zu" , len);
78
134
79
135
switch (write_logic) {
136
+ case HDLCWriteLogic::OnFlush:
137
+ for (int j = 0 ; j < len; j++) {
138
+ bool ok = frame_buffer.write (data[j]);
139
+ assert (ok);
140
+ if (frame_buffer.isFull ()) {
141
+ LOGE (" Buffer full - increase size!" );
142
+ }
143
+ }
144
+ break ;
80
145
case HDLCWriteLogic::OnBufferFull:
81
146
for (int j = 0 ; j < len; j++) {
82
147
bool ok = frame_buffer.write (data[j]);
83
148
assert (ok);
84
- if (frame_buffer.available () == max_frame_length ) {
149
+ if (frame_buffer.isFull () ) {
85
150
sendFrame (frame_buffer.data (), max_frame_length);
86
151
frame_buffer.reset ();
87
152
}
88
153
}
89
154
break ;
90
- case HDLCWriteLogic::onWrite :
155
+ case HDLCWriteLogic::OnWrite :
91
156
sendFrame (data, len);
92
157
break ;
93
158
}
94
159
95
160
return len;
96
161
}
97
162
163
+ /* *
164
+ * @brief Flush any buffered data as an HDLC frame
165
+ *
166
+ * If data is pending in the buffer, encodes and sends it as an HDLC frame.
167
+ * Also flushes the underlying output stream if available.
168
+ */
98
169
void flush () override {
99
170
LOGD (" HDLCStream::flush" );
100
171
if (frame_buffer.available () > 0 ) {
@@ -106,45 +177,94 @@ class HDLCStream : public Stream {
106
177
}
107
178
}
108
179
180
+ /* *
181
+ * @brief Get number of bytes available for reading
182
+ *
183
+ * @return int Maximum frame length if input stream is available, 0 otherwise
184
+ */
109
185
int available () override { return p_in == nullptr ? 0 : max_frame_length; }
110
186
111
- // / Provides the decoded data
187
+ /* *
188
+ * @brief Read decoded data from the next valid HDLC frame
189
+ *
190
+ * Processes incoming bytes through the HDLC protocol decoder until a
191
+ * complete valid frame is found or timeout occurs.
192
+ *
193
+ * @param data Buffer to store the decoded data
194
+ * @param len Maximum number of bytes to read
195
+ * @return size_t Number of bytes actually read (0 if no valid frame found)
196
+ */
112
197
size_t readBytes (uint8_t *data, size_t len) override {
113
198
if (p_in == nullptr ) {
114
199
LOGI (" No data source" );
115
200
return 0 ;
116
201
}
117
202
118
203
int result = 0 ;
119
- // process bytes from input
120
- while (result == 0 ) {
204
+ unsigned long startTime = millis ();
205
+
206
+ // process bytes from input with timeout
207
+ while (result == 0 && (millis () - startTime < timeout_ms)) {
208
+ // Check if data is available before blocking on read
209
+ if (p_in->available () <= 0 ) {
210
+ continue ;
211
+ }
212
+
121
213
int ch = p_in->read ();
122
- // ch is -1 when no data
123
214
if (ch >= 0 ) {
124
215
result = charReceiver (ch);
125
216
if (result > 0 ) {
217
+ // Respect the requested length limit
218
+ result = min (result, (int )len);
126
219
result = frame_buffer.readArray (data, result);
127
220
break ;
128
221
}
129
222
} else {
130
223
break ;
131
224
}
132
225
}
226
+
133
227
LOGD (" HDLCStream::readBytes: %zu -> %d" , len, result);
134
228
return result;
135
229
}
136
-
230
+
231
+ /* *
232
+ * @brief Set both input and output streams to the same object
233
+ *
234
+ * @param io Stream object for bidirectional HDLC communication
235
+ */
137
236
void setStream (Stream &io) {
138
237
p_out = &io;
139
238
p_in = &io;
140
239
}
141
240
241
+ /* *
242
+ * @brief Set output stream only for write-only operation
243
+ *
244
+ * @param out Stream object for HDLC output
245
+ */
142
246
void setStream (Print &out) { p_out = &out; }
143
247
248
+ /* *
249
+ * @brief Set output destination for encoded HDLC frames
250
+ *
251
+ * @param out Print object to receive encoded HDLC data
252
+ */
144
253
void setOutput (Print &out) { p_out = &out; }
145
254
255
+ /* *
256
+ * @brief Write a single byte to be encoded in an HDLC frame
257
+ *
258
+ * @param ch Byte to write
259
+ * @return size_t Always returns 1 if successful
260
+ */
146
261
size_t write (uint8_t ch) override { return write (&ch, 1 ); };
147
262
263
+ /* *
264
+ * @brief Read a single byte from the decoded HDLC data
265
+ *
266
+ * @return int Byte value (0-255) or -1 if no data available
267
+ */
148
268
int read () override {
149
269
uint8_t c[1 ];
150
270
if (readBytes (c, 1 ) == 0 ) {
@@ -153,10 +273,23 @@ class HDLCStream : public Stream {
153
273
return c[0 ];
154
274
}
155
275
156
- // / not supported
276
+ /* *
277
+ * @brief Not supported in this implementation
278
+ *
279
+ * @return int Always returns -1
280
+ */
157
281
int peek () override { return -1 ; }
158
282
159
- // / Defines the framing logic for writing data
283
+ /* *
284
+ * @brief Set the framing logic for writing data
285
+ *
286
+ * Controls when data is actually encoded and sent as HDLC frames:
287
+ * - OnBufferFull: When internal buffer is full
288
+ * - OnFlush: When flush() is called
289
+ * - OnWrite: Immediately on each write
290
+ *
291
+ * @param logic The framing logic to use
292
+ */
160
293
void setWriteLogic (HDLCWriteLogic logic) { write_logic = logic; }
161
294
162
295
private:
@@ -213,7 +346,6 @@ class HDLCStream : public Stream {
213
346
214
347
if (frame_position == max_frame_length) {
215
348
frame_position = 0 ;
216
- frame_checksum = CRC16_CCITT_INIT_VAL;
217
349
}
218
350
return result;
219
351
}
@@ -252,50 +384,21 @@ class HDLCStream : public Stream {
252
384
p_out->flush ();
253
385
}
254
386
255
- static uint16_t crc16_update (uint16_t crc, uint8_t a) {
256
- int i;
257
- crc ^= a;
258
- for (i = 0 ; i < 8 ; ++i) {
259
- if (crc & 1 )
260
- crc = (crc >> 1 ) ^ 0xA001 ;
261
- else
262
- crc = (crc >> 1 );
263
- }
264
- return crc;
265
- }
266
-
267
- static uint16_t crc_xmodem_update (uint16_t crc, uint8_t data) {
268
- int i;
269
-
270
- crc = crc ^ ((uint16_t )data << 8 );
271
- for (i = 0 ; i < 8 ; i++) {
272
- if (crc & 0x8000 )
273
- crc = (crc << 1 ) ^ 0x1021 ;
274
- else
275
- crc <<= 1 ;
276
- }
277
-
278
- return crc;
279
- }
280
-
387
+ /* *
388
+ * @brief Calculate CRC-CCITT checksum
389
+ *
390
+ * Implementation of CRC-CCITT (0xFFFF) calculation used for HDLC frames
391
+ *
392
+ * @param crc Current CRC value
393
+ * @param data Byte to include in CRC calculation
394
+ * @return uint16_t Updated CRC value
395
+ */
281
396
static uint16_t _crc_ccitt_update (uint16_t crc, uint8_t data) {
282
397
data ^= lo8 (crc);
283
398
data ^= data << 4 ;
284
399
return ((((uint16_t )data << 8 ) | hi8 (crc)) ^ (uint8_t )(data >> 4 ) ^
285
400
((uint16_t )data << 3 ));
286
401
}
287
-
288
- static uint8_t _crc_ibutton_update (uint8_t crc, uint8_t data) {
289
- uint8_t i;
290
- crc = crc ^ data;
291
- for (i = 0 ; i < 8 ; i++) {
292
- if (crc & 0x01 )
293
- crc = (crc >> 1 ) ^ 0x8C ;
294
- else
295
- crc >>= 1 ;
296
- }
297
- return crc;
298
- }
299
402
};
300
403
301
404
} // namespace audio_tools
0 commit comments