blob: e323224ca0bd273edbe86f265a42f1e2cee2e361 [file] [log] [blame]
Alexei Frolovd3e5cb72021-01-08 13:08:45 -08001.. _module-pw_hdlc:
Wyatt Hepler455b4922020-09-18 00:19:21 -07002
Alexei Frolovd3e5cb72021-01-08 13:08:45 -08003-------
4pw_hdlc
5-------
Wyatt Hepler455b4922020-09-18 00:19:21 -07006`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>`_.
shaneajg0869f2c2020-07-08 10:39:14 -040010
Alexei Frolovd3e5cb72021-01-08 13:08:45 -080011The ``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.
shaneajg0869f2c2020-07-08 10:39:14 -040015
Alexei Frolovd3e5cb72021-01-08 13:08:45 -080016**Why use the pw_hdlc module?**
shaneajgf8325f92020-08-05 12:35:10 -040017
Wyatt Hepler455b4922020-09-18 00:19:21 -070018 * 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.
shaneajgf8325f92020-08-05 12:35:10 -040023
Wyatt Hepler455b4922020-09-18 00:19:21 -070024.. admonition:: Try it out!
shaneajgf8325f92020-08-05 12:35:10 -040025
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -070026 For an example of how to use HDLC with :ref:`module-pw_rpc`, see the
Alexei Frolovd3e5cb72021-01-08 13:08:45 -080027 :ref:`module-pw_hdlc-rpc-example`.
shaneajgf8325f92020-08-05 12:35:10 -040028
Wyatt Hepler455b4922020-09-18 00:19:21 -070029.. toctree::
30 :maxdepth: 1
31 :hidden:
shaneajgf8325f92020-08-05 12:35:10 -040032
Wyatt Hepler455b4922020-09-18 00:19:21 -070033 rpc_example/docs
shaneajgf8325f92020-08-05 12:35:10 -040034
35Protocol Description
36====================
37
Wyatt Hepler455b4922020-09-18 00:19:21 -070038Frames
39------
Alexei Frolovd3e5cb72021-01-08 13:08:45 -080040The HDLC implementation in ``pw_hdlc`` supports only HDLC unnumbered
Alexei Frolov6053c312020-12-09 22:43:55 -080041information frames. These frames are encoded as follows:
Wyatt Hepler455b4922020-09-18 00:19:21 -070042
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
shaneajgf8325f92020-08-05 12:35:10 -040057Encoding 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,
Wyatt Hepler455b4922020-09-18 00:19:21 -070061the special bytes are escaped:
shaneajgf8325f92020-08-05 12:35:10 -040062
Wyatt Hepler455b4922020-09-18 00:19:21 -070063 +-------------------------+-----------------------+
64 | Unescaped Special Bytes | Escaped Special Bytes |
65 +=========================+=======================+
66 | 7E | 7D 5E |
67 +-------------------------+-----------------------+
68 | 7D | 7D 5D |
69 +-------------------------+-----------------------+
shaneajgf8325f92020-08-05 12:35:10 -040070
71The bytes of the payload are escaped and written in a single pass. The
Wyatt Hepler455b4922020-09-18 00:19:21 -070072frame 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.
shaneajgf8325f92020-08-05 12:35:10 -040074
75Decoding received bytes
76-----------------------
Wyatt Hepler455b4922020-09-18 00:19:21 -070077Frames may be received in multiple parts, so we need to store the received data
shaneajgf8325f92020-08-05 12:35:10 -040078in a buffer until the ending frame delimiter (0x7E) is read. When the
Alexei Frolovd3e5cb72021-01-08 13:08:45 -080079``pw_hdlc`` decoder receives data, it unescapes it and adds it to a buffer.
Wyatt Hepler455b4922020-09-18 00:19:21 -070080When the frame is complete, it calculates and verifies the frame check sequence
81and does the following:
shaneajgf8325f92020-08-05 12:35:10 -040082
Wyatt Hepler455b4922020-09-18 00:19:21 -070083* 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.
shaneajgf8325f92020-08-05 12:35:10 -040086
87API Usage
88=========
Alexei Frolovd3e5cb72021-01-08 13:08:45 -080089There are two primary functions of the ``pw_hdlc`` module:
Wyatt Hepler455b4922020-09-18 00:19:21 -070090
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.
shaneajgf8325f92020-08-05 12:35:10 -040095
96Encoder
97-------
Wyatt Hepler455b4922020-09-18 00:19:21 -070098The Encoder API provides a single function that encodes data as an HDLC
Alexei Frolov6053c312020-12-09 22:43:55 -080099unnumbered information frame.
shaneajgf8325f92020-08-05 12:35:10 -0400100
101C++
102^^^
Wyatt Hepler455b4922020-09-18 00:19:21 -0700103.. cpp:namespace:: pw
104
Alexei Frolovb9fda582021-03-13 18:02:52 -0800105.. cpp:function:: Status hdlc::WriteUIFrame(uint64_t address, ConstByteSpan data, stream::Writer& writer)
Wyatt Hepler455b4922020-09-18 00:19:21 -0700106
Wyatt Heplerf9fb90f2020-09-30 18:59:33 -0700107 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`
Wyatt Hepler455b4922020-09-18 00:19:21 -0700109 module to compute the CRC-32 frame check sequence.
shaneajgf8325f92020-08-05 12:35:10 -0400110
111.. code-block:: cpp
112
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800113 #include "pw_hdlc/encoder.h"
114 #include "pw_hdlc/sys_io_stream.h"
shaneajgf8325f92020-08-05 12:35:10 -0400115
116 int main() {
Wyatt Hepler455b4922020-09-18 00:19:21 -0700117 pw::stream::SysIoWriter serial_writer;
Alexei Frolov6053c312020-12-09 22:43:55 -0800118 Status status = WriteUIFrame(123 /* address */,
119 data,
120 serial_writer);
Wyatt Hepler455b4922020-09-18 00:19:21 -0700121 if (!status.ok()) {
122 PW_LOG_INFO("Writing frame failed! %s", status.str());
123 }
shaneajgf8325f92020-08-05 12:35:10 -0400124 }
125
shaneajgf8325f92020-08-05 12:35:10 -0400126Python
127^^^^^^
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800128.. automodule:: pw_hdlc.encode
Wyatt Hepler455b4922020-09-18 00:19:21 -0700129 :members:
shaneajgf8325f92020-08-05 12:35:10 -0400130
131.. code-block:: python
132
133 import serial
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800134 from pw_hdlc import encode
shaneajgf8325f92020-08-05 12:35:10 -0400135
136 ser = serial.Serial()
Paul Mathieu94705a92021-05-13 15:07:41 -0700137 address = 123
138 ser.write(encode.ui_frame(address, b'your data here!'))
shaneajgf8325f92020-08-05 12:35:10 -0400139
140Decoder
141-------
Wyatt Hepler455b4922020-09-18 00:19:21 -0700142The decoder class unescapes received bytes and adds them to a buffer. Complete,
143valid HDLC frames are yielded as they are received.
shaneajgf8325f92020-08-05 12:35:10 -0400144
145C++
146^^^
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800147.. cpp:class:: pw::hdlc::Decoder
shaneajgf8325f92020-08-05 12:35:10 -0400148
Wyatt Hepler455b4922020-09-18 00:19:21 -0700149 .. cpp:function:: pw::Result<Frame> Process(std::byte b)
shaneajgf8325f92020-08-05 12:35:10 -0400150
Wyatt Hepler455b4922020-09-18 00:19:21 -0700151 Parses a single byte of an HDLC stream. Returns a Result with the complete
152 frame if the byte completes a frame. The status is the following:
153
154 - OK - A frame was successfully decoded. The Result contains the Frame,
155 which is invalidated by the next Process call.
156 - UNAVAILABLE - No frame is available.
157 - RESOURCE_EXHAUSTED - A frame completed, but it was too large to fit in
158 the decoder's buffer.
159 - DATA_LOSS - A frame completed, but it was invalid. The frame was
160 incomplete or the frame check sequence verification failed.
161
162 .. cpp:function:: void Process(pw::ConstByteSpan data, F&& callback, Args&&... args)
163
164 Processes a span of data and calls the provided callback with each frame or
165 error.
166
167This example demonstrates reading individual bytes from ``pw::sys_io`` and
168decoding HDLC frames:
shaneajgf8325f92020-08-05 12:35:10 -0400169
170.. code-block:: cpp
171
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800172 #include "pw_hdlc/decoder.h"
shaneajgf8325f92020-08-05 12:35:10 -0400173 #include "pw_sys_io/sys_io.h"
174
175 int main() {
Wyatt Hepler455b4922020-09-18 00:19:21 -0700176 std::byte data;
shaneajgf8325f92020-08-05 12:35:10 -0400177 while (true) {
178 if (!pw::sys_io::ReadByte(&data).ok()) {
179 // Log serial reading error
180 }
Wyatt Hepler455b4922020-09-18 00:19:21 -0700181 Result<Frame> decoded_frame = decoder.Process(data);
shaneajgf8325f92020-08-05 12:35:10 -0400182
Wyatt Hepler455b4922020-09-18 00:19:21 -0700183 if (decoded_frame.ok()) {
184 // Handle the decoded frame
shaneajgf8325f92020-08-05 12:35:10 -0400185 }
186 }
187 }
188
189Python
190^^^^^^
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800191.. autoclass:: pw_hdlc.decode.FrameDecoder
Wyatt Hepler455b4922020-09-18 00:19:21 -0700192 :members:
shaneajgf8325f92020-08-05 12:35:10 -0400193
Wyatt Hepler455b4922020-09-18 00:19:21 -0700194Below is an example using the decoder class to decode data read from serial:
shaneajgf8325f92020-08-05 12:35:10 -0400195
196.. code-block:: python
197
198 import serial
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800199 from pw_hdlc import decode
shaneajgf8325f92020-08-05 12:35:10 -0400200
201 ser = serial.Serial()
Wyatt Hepler455b4922020-09-18 00:19:21 -0700202 decoder = decode.FrameDecoder()
shaneajgf8325f92020-08-05 12:35:10 -0400203
Wyatt Hepler455b4922020-09-18 00:19:21 -0700204 while True:
205 for frame in decoder.process_valid_frames(ser.read()):
206 # Handle the decoded frame
shaneajgf8325f92020-08-05 12:35:10 -0400207
Wyatt Hepler455b4922020-09-18 00:19:21 -0700208Additional features
209===================
shaneajgf8325f92020-08-05 12:35:10 -0400210
Wyatt Hepler455b4922020-09-18 00:19:21 -0700211pw::stream::SysIoWriter
shaneajg0869f2c2020-07-08 10:39:14 -0400212------------------------
Wyatt Hepler455b4922020-09-18 00:19:21 -0700213The ``SysIoWriter`` C++ class implements the ``Writer`` interface with
214``pw::sys_io``. This Writer may be used by the C++ encoder to send HDLC frames
215over serial.
shaneajg0869f2c2020-07-08 10:39:14 -0400216
Wyatt Hepler455b4922020-09-18 00:19:21 -0700217HdlcRpcClient
218-------------
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800219.. autoclass:: pw_hdlc.rpc.HdlcRpcClient
Wyatt Hepler455b4922020-09-18 00:19:21 -0700220 :members:
shaneajg0869f2c2020-07-08 10:39:14 -0400221
Wyatt Hepler455b4922020-09-18 00:19:21 -0700222Roadmap
223=======
Alexei Frolovd3e5cb72021-01-08 13:08:45 -0800224- **Expanded protocol support** - ``pw_hdlc`` currently only supports
Alexei Frolovb9fda582021-03-13 18:02:52 -0800225 unnumbered information frames. Support for different frame types and
226 extended control fields may be added in the future.
shaneajgf8325f92020-08-05 12:35:10 -0400227
228- **Higher performance** - We plan to improve the overall performance of the
229 decoder and encoder implementations by using SIMD/NEON.
Wyatt Hepler455b4922020-09-18 00:19:21 -0700230
231Compatibility
232=============
233C++17