Skip to content

Commit 60d25f3

Browse files
committed
HDLCStream
1 parent afd73d4 commit 60d25f3

File tree

6 files changed

+387
-419
lines changed

6 files changed

+387
-419
lines changed
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
#pragma once
2+
3+
#include <stdbool.h>
4+
#include <stdint.h>
5+
6+
#include "AudioTools/CoreAudio/Buffers.h"
7+
#include "AudioToolsConfig.h"
8+
9+
namespace audio_tools {
10+
11+
/**
12+
* @brief High-Level Data Link Control (HDLC) is a bit-oriented code-transparent
13+
* synchronous data link layer protocol for reliable, framed, and error-checked
14+
* communication.
15+
*
16+
* This class implements HDLC framing with:
17+
* - Frame delimiter (0x7E) for marking frame boundaries
18+
* - Byte stuffing to escape special characters in the data
19+
* - 16-bit CRC-CCITT for error detection
20+
* - Transparent Stream interface for easy integration
21+
*
22+
* @ingroup communications
23+
* @author Phil Schatzmann
24+
*/
25+
26+
class HDLCStream : public Stream {
27+
public:
28+
/**
29+
* @brief Construct a new HDLCStream object using a Stream for input and output
30+
*
31+
* @param stream The underlying Stream for both reading and writing
32+
* @param maxFrameSize Maximum size of a single HDLC frame
33+
*/
34+
HDLCStream(Stream& stream, size_t maxFrameSize)
35+
: _maxFrameSize(maxFrameSize) {
36+
p_stream = &stream;
37+
p_print = &stream;
38+
tx_frame_buffer.resize(maxFrameSize);
39+
rx_frame_buffer.resize(maxFrameSize);
40+
_rxBuffer.resize(maxFrameSize);
41+
}
42+
43+
/**
44+
* @brief Construct a new HDLCStream object using a Print for output only
45+
*
46+
* @param stream The underlying Print for writing
47+
* @param maxFrameSize Maximum size of a single HDLC frame
48+
*/
49+
HDLCStream(Print& stream, size_t maxFrameSize) : _maxFrameSize(maxFrameSize) {
50+
p_print = &stream;
51+
tx_frame_buffer.resize(maxFrameSize);
52+
rx_frame_buffer.resize(maxFrameSize);
53+
_rxBuffer.resize(maxFrameSize);
54+
}
55+
56+
/**
57+
* @brief Get the number of bytes available to read from the frame buffer
58+
*
59+
* @return int Number of bytes available
60+
*/
61+
int available() override {
62+
if (rx_frame_buffer.available() == 0) _processInput();
63+
return rx_frame_buffer.available();
64+
}
65+
66+
/**
67+
* @brief Not supported
68+
*
69+
* @return -1
70+
*/
71+
int read() override { return -1; }
72+
73+
/**
74+
* @brief Read a full frame from the stream into a buffer
75+
*
76+
* @param buffer Destination buffer to hold the data
77+
* @param length Maximum number of bytes to read
78+
* @return size_t Actual number of bytes read
79+
*/
80+
size_t readBytes(uint8_t* buffer, size_t length) override {
81+
size_t available_bytes = rx_frame_buffer.available();
82+
// get more data
83+
if (available_bytes == 0) {
84+
_processInput();
85+
available_bytes = rx_frame_buffer.available();
86+
}
87+
88+
// check that we consume the full frame
89+
if (length < available_bytes) {
90+
LOGE("readBytes len too small %u instead of %u", (unsigned)length,
91+
(unsigned)available_bytes);
92+
return 0;
93+
}
94+
95+
// provide the data
96+
memcpy(buffer, rx_frame_buffer.data(), available_bytes);
97+
rx_frame_buffer.clear();
98+
_frameReady = false;
99+
return available_bytes;
100+
}
101+
102+
/**
103+
* @brief Not supported
104+
*
105+
* @return -1
106+
*/
107+
int peek() override { return -1; }
108+
109+
/**
110+
* @brief Flush the output buffer of the underlying stream
111+
*/
112+
void flush() override { p_stream->flush(); }
113+
114+
/**
115+
* @brief Not supported
116+
*
117+
* @param b The byte to write
118+
* @return 0
119+
*/
120+
size_t write(uint8_t b) override { return 0; }
121+
122+
/**
123+
* @brief Write multiple bytes to the stream
124+
*
125+
* @param data Pointer to the data buffer
126+
* @param len Number of bytes to write
127+
* @return size_t Number of bytes written
128+
*/
129+
size_t write(const uint8_t* data, size_t len) override {
130+
return writeFrame(data, len);
131+
}
132+
133+
protected:
134+
Stream* p_stream = nullptr;
135+
Print* p_print = nullptr;
136+
const size_t _maxFrameSize;
137+
SingleBuffer<uint8_t> tx_frame_buffer;
138+
SingleBuffer<uint8_t> rx_frame_buffer;
139+
Vector<uint8_t> _rxBuffer;
140+
size_t _frameLen = 0;
141+
size_t _rxLen = 0;
142+
size_t _rxPos = 0;
143+
bool _frameReady = false;
144+
145+
enum RxState { IDLE, RECEIVING, ESCAPED } _rxState = IDLE;
146+
147+
static constexpr uint8_t HDLC_FLAG = 0x7E;
148+
static constexpr uint8_t HDLC_ESC = 0x7D;
149+
static constexpr uint8_t HDLC_ESC_XOR = 0x20;
150+
151+
/**
152+
* @brief Calculate CRC-CCITT (16-bit)
153+
*
154+
* @param data Byte to include in CRC calculation
155+
* @param crc Current CRC value
156+
* @return uint16_t Updated CRC value
157+
*/
158+
uint16_t _crc16(uint8_t data, uint16_t crc) {
159+
crc ^= (uint16_t)data << 8;
160+
for (int i = 0; i < 8; i++)
161+
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : (crc << 1);
162+
return crc;
163+
}
164+
165+
/**
166+
* @brief Write a byte with proper HDLC byte stuffing if needed
167+
*
168+
* @param b Byte to write
169+
*/
170+
void _writeEscaped(uint8_t b) {
171+
if (b == HDLC_FLAG || b == HDLC_ESC) {
172+
tx_frame_buffer.write(HDLC_ESC);
173+
tx_frame_buffer.write(b ^ HDLC_ESC_XOR);
174+
} else {
175+
tx_frame_buffer.write(b);
176+
}
177+
}
178+
179+
/**
180+
* @brief Write a complete HDLC frame with proper framing and CRC
181+
*
182+
* @param data Data to be framed
183+
* @param len Length of data
184+
* @return size_t Number of bytes in the original data
185+
*/
186+
size_t writeFrame(const uint8_t* data, size_t len) {
187+
if (!data || len == 0) return 0;
188+
189+
uint16_t crc = 0xFFFF;
190+
tx_frame_buffer.write(HDLC_FLAG);
191+
192+
for (size_t i = 0; i < len; ++i) {
193+
crc = _crc16(data[i], crc);
194+
_writeEscaped(data[i]);
195+
}
196+
197+
_writeEscaped(crc >> 8);
198+
_writeEscaped(crc & 0xFF);
199+
tx_frame_buffer.write(HDLC_FLAG);
200+
p_print->write(tx_frame_buffer.data(), tx_frame_buffer.available());
201+
p_print->flush();
202+
tx_frame_buffer.clear();
203+
return len;
204+
}
205+
206+
/**
207+
* @brief Process incoming bytes, detect frames, validate CRC and prepare data
208+
* for reading
209+
*/
210+
void _processInput() {
211+
while (!_frameReady && p_stream->available()) {
212+
uint8_t b = p_stream->read();
213+
214+
if (b == HDLC_FLAG) {
215+
if (_rxLen >= 3) {
216+
uint16_t recvCrc =
217+
(_rxBuffer[_rxLen - 2] << 8) | _rxBuffer[_rxLen - 1];
218+
uint16_t calcCrc = 0xFFFF;
219+
for (size_t i = 0; i < _rxLen - 2; ++i)
220+
calcCrc = _crc16(_rxBuffer[i], calcCrc);
221+
222+
if (calcCrc == recvCrc) {
223+
for (int j = 0; j < _rxLen - 2; j++) {
224+
rx_frame_buffer.write(_rxBuffer[j]);
225+
}
226+
227+
_frameLen = _rxLen - 2;
228+
_rxPos = 0;
229+
_frameReady = true;
230+
}
231+
}
232+
_rxState = IDLE;
233+
_rxLen = 0;
234+
continue;
235+
}
236+
237+
switch (_rxState) {
238+
case IDLE:
239+
_rxLen = 0;
240+
if (b == HDLC_ESC) {
241+
_rxState = ESCAPED;
242+
} else {
243+
_rxState = RECEIVING;
244+
if (_rxLen < _maxFrameSize) _rxBuffer[_rxLen++] = b;
245+
}
246+
break;
247+
248+
case RECEIVING:
249+
if (b == HDLC_ESC) {
250+
_rxState = ESCAPED;
251+
} else if (_rxLen < _maxFrameSize) {
252+
_rxBuffer[_rxLen++] = b;
253+
}
254+
break;
255+
256+
case ESCAPED:
257+
if (_rxLen < _maxFrameSize) {
258+
_rxBuffer[_rxLen++] = b ^ HDLC_ESC_XOR;
259+
}
260+
_rxState = RECEIVING;
261+
break;
262+
}
263+
264+
if (_rxLen >= _maxFrameSize) {
265+
_rxState = IDLE; // overflow
266+
_rxLen = 0;
267+
}
268+
}
269+
}
270+
};
271+
272+
} // namespace audio_tools

0 commit comments

Comments
 (0)