• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1.. _module-pw_hdlc:
2
3-------
4pw_hdlc
5-------
6`High-Level Data Link Control (HDLC)
7<https://en.wikipedia.org/wiki/High-Level_Data_Link_Control>`_ is a data link
8layer protocol intended for serial communication between devices. HDLC is
9standardized as `ISO/IEC 13239:2002 <https://www.iso.org/standard/37010.html>`_.
10
11The ``pw_hdlc`` module provides a simple, robust frame-oriented transport that
12uses a subset of the HDLC protocol. ``pw_hdlc`` supports sending between
13embedded devices or the host. It can be used with :ref:`module-pw_rpc` to enable
14remote procedure calls (RPCs) on embedded on devices.
15
16**Why use the pw_hdlc module?**
17
18  * Enables the transmission of RPCs and other data between devices over serial.
19  * Detects corruption and data loss.
20  * Light-weight, simple, and easy to use.
21  * Supports streaming to transport without buffering, since the length is not
22    encoded.
23
24.. admonition:: Try it out!
25
26  For an example of how to use HDLC with :ref:`module-pw_rpc`, see the
27  :ref:`module-pw_hdlc-rpc-example`.
28
29.. toctree::
30  :maxdepth: 1
31  :hidden:
32
33  rpc_example/docs
34
35Protocol Description
36====================
37
38Frames
39------
40The HDLC implementation in ``pw_hdlc`` supports only HDLC unnumbered
41information frames. These frames are encoded as follows:
42
43.. code-block:: text
44
45    _________________________________________
46    | | | |                          |    | |...
47    | | | |                          |    | |... [More frames]
48    |_|_|_|__________________________|____|_|...
49     F A C       Payload              FCS  F
50
51     F = flag byte (0x7e, the ~ character)
52     A = address field
53     C = control field
54     FCS = frame check sequence (CRC-32)
55
56
57Encoding and sending data
58-------------------------
59This module first writes an initial frame delimiter byte (0x7E) to indicate the
60beginning of the frame. Before sending any of the payload data through serial,
61the special bytes are escaped:
62
63            +-------------------------+-----------------------+
64            | Unescaped Special Bytes | Escaped Special Bytes |
65            +=========================+=======================+
66            |           7E            |        7D 5E          |
67            +-------------------------+-----------------------+
68            |           7D            |        7D 5D          |
69            +-------------------------+-----------------------+
70
71The bytes of the payload are escaped and written in a single pass. The
72frame check sequence is calculated, escaped, and written after. After this, a
73final frame delimiter byte (0x7E) is written to mark the end of the frame.
74
75Decoding received bytes
76-----------------------
77Frames may be received in multiple parts, so we need to store the received data
78in a buffer until the ending frame delimiter (0x7E) is read. When the
79``pw_hdlc`` decoder receives data, it unescapes it and adds it to a buffer.
80When the frame is complete, it calculates and verifies the frame check sequence
81and does the following:
82
83* If correctly verified, the decoder returns the decoded frame.
84* If the checksum verification fails, the frame is discarded and an error is
85  reported.
86
87API Usage
88=========
89There are two primary functions of the ``pw_hdlc`` module:
90
91  * **Encoding** data by constructing a frame with the escaped payload bytes and
92    frame check sequence.
93  * **Decoding** data by unescaping the received bytes, verifying the frame
94    check sequence, and returning successfully decoded frames.
95
96Encoder
97-------
98The Encoder API provides a single function that encodes data as an HDLC
99unnumbered information frame.
100
101C++
102^^^
103.. cpp:namespace:: pw
104
105.. cpp:function:: Status hdlc::WriteUIFrame(uint64_t address, ConstByteSpan data, stream::Writer& writer)
106
107  Writes a span of data to a :ref:`pw::stream::Writer <module-pw_stream>` and
108  returns the status. This implementation uses the :ref:`module-pw_checksum`
109  module to compute the CRC-32 frame check sequence.
110
111.. code-block:: cpp
112
113  #include "pw_hdlc/encoder.h"
114  #include "pw_hdlc/sys_io_stream.h"
115
116  int main() {
117    pw::stream::SysIoWriter serial_writer;
118    Status status = WriteUIFrame(123 /* address */,
119                                 data,
120                                 serial_writer);
121    if (!status.ok()) {
122      PW_LOG_INFO("Writing frame failed! %s", status.str());
123    }
124  }
125
126Python
127^^^^^^
128.. automodule:: pw_hdlc.encode
129  :members:
130
131.. code-block:: python
132
133  import serial
134  from pw_hdlc import encode
135
136  ser = serial.Serial()
137  address = 123
138  ser.write(encode.ui_frame(address, b'your data here!'))
139
140Typescript
141^^^^^^^^^^
142
143Encoder
144-------
145The Encoder class provides a way to build complete, escaped HDLC UI frames.
146
147.. js:method:: Encoder.uiFrame(address, data)
148
149    :param number address: frame address.
150    :param Uint8Array data: frame data.
151    :returns: Uint8Array containing a complete HDLC frame.
152
153Decoder
154-------
155The decoder class unescapes received bytes and adds them to a buffer. Complete,
156valid HDLC frames are yielded as they are received.
157
158.. js:method:: Decoder.process(bytes)
159
160    :param Uint8Array bytes: bytes received from the medium.
161    :yields: Frame complete frames.
162
163C++
164^^^
165.. cpp:class:: pw::hdlc::Decoder
166
167  .. cpp:function:: pw::Result<Frame> Process(std::byte b)
168
169    Parses a single byte of an HDLC stream. Returns a Result with the complete
170    frame if the byte completes a frame. The status is the following:
171
172      - OK - A frame was successfully decoded. The Result contains the Frame,
173        which is invalidated by the next Process call.
174      - UNAVAILABLE - No frame is available.
175      - RESOURCE_EXHAUSTED - A frame completed, but it was too large to fit in
176        the decoder's buffer.
177      - DATA_LOSS - A frame completed, but it was invalid. The frame was
178        incomplete or the frame check sequence verification failed.
179
180  .. cpp:function:: void Process(pw::ConstByteSpan data, F&& callback, Args&&... args)
181
182    Processes a span of data and calls the provided callback with each frame or
183    error.
184
185This example demonstrates reading individual bytes from ``pw::sys_io`` and
186decoding HDLC frames:
187
188.. code-block:: cpp
189
190  #include "pw_hdlc/decoder.h"
191  #include "pw_sys_io/sys_io.h"
192
193  int main() {
194    std::byte data;
195    while (true) {
196      if (!pw::sys_io::ReadByte(&data).ok()) {
197        // Log serial reading error
198      }
199      Result<Frame> decoded_frame = decoder.Process(data);
200
201      if (decoded_frame.ok()) {
202        // Handle the decoded frame
203      }
204    }
205  }
206
207Python
208^^^^^^
209.. autoclass:: pw_hdlc.decode.FrameDecoder
210  :members:
211
212Below is an example using the decoder class to decode data read from serial:
213
214.. code-block:: python
215
216  import serial
217  from pw_hdlc import decode
218
219  ser = serial.Serial()
220  decoder = decode.FrameDecoder()
221
222  while True:
223      for frame in decoder.process_valid_frames(ser.read()):
224          # Handle the decoded frame
225
226Typescript
227^^^^^^^^^^
228
229Decodes one or more HDLC frames from a stream of data.
230
231.. js:method:: process(data)
232
233    :param Uint8Array data: bytes to be decoded.
234    :yields: HDLC frames, including corrupt frames.
235      The Frame.ok() method whether the frame is valid.
236
237.. js:method:: processValidFrames(data)
238
239    :param Uint8Array data: bytes to be decoded.
240    :yields: Valid HDLC frames, logging any errors.
241
242Additional features
243===================
244
245pw::stream::SysIoWriter
246------------------------
247The ``SysIoWriter`` C++ class implements the ``Writer`` interface with
248``pw::sys_io``. This Writer may be used by the C++ encoder to send HDLC frames
249over serial.
250
251HdlcRpcClient
252-------------
253.. autoclass:: pw_hdlc.rpc.HdlcRpcClient
254  :members:
255
256.. autoclass:: pw_hdlc.rpc.HdlcRpcLocalServerAndClient
257  :members:
258
259Roadmap
260=======
261- **Expanded protocol support** - ``pw_hdlc`` currently only supports
262  unnumbered information frames. Support for different frame types and
263  extended control fields may be added in the future.
264
265- **Higher performance** - We plan to improve the overall performance of the
266  decoder and encoder implementations by using SIMD/NEON.
267
268Compatibility
269=============
270C++17
271
272Zephyr
273======
274To enable ``pw_hdlc`` for Zephyr add ``CONFIG_PIGWEED_HDLC=y`` to the project's
275configuration.
276