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 ser.write(encode.ui_frame(b'your data here!')) 138 139Decoder 140------- 141The decoder class unescapes received bytes and adds them to a buffer. Complete, 142valid HDLC frames are yielded as they are received. 143 144C++ 145^^^ 146.. cpp:class:: pw::hdlc::Decoder 147 148 .. cpp:function:: pw::Result<Frame> Process(std::byte b) 149 150 Parses a single byte of an HDLC stream. Returns a Result with the complete 151 frame if the byte completes a frame. The status is the following: 152 153 - OK - A frame was successfully decoded. The Result contains the Frame, 154 which is invalidated by the next Process call. 155 - UNAVAILABLE - No frame is available. 156 - RESOURCE_EXHAUSTED - A frame completed, but it was too large to fit in 157 the decoder's buffer. 158 - DATA_LOSS - A frame completed, but it was invalid. The frame was 159 incomplete or the frame check sequence verification failed. 160 161 .. cpp:function:: void Process(pw::ConstByteSpan data, F&& callback, Args&&... args) 162 163 Processes a span of data and calls the provided callback with each frame or 164 error. 165 166This example demonstrates reading individual bytes from ``pw::sys_io`` and 167decoding HDLC frames: 168 169.. code-block:: cpp 170 171 #include "pw_hdlc/decoder.h" 172 #include "pw_sys_io/sys_io.h" 173 174 int main() { 175 std::byte data; 176 while (true) { 177 if (!pw::sys_io::ReadByte(&data).ok()) { 178 // Log serial reading error 179 } 180 Result<Frame> decoded_frame = decoder.Process(data); 181 182 if (decoded_frame.ok()) { 183 // Handle the decoded frame 184 } 185 } 186 } 187 188Python 189^^^^^^ 190.. autoclass:: pw_hdlc.decode.FrameDecoder 191 :members: 192 193Below is an example using the decoder class to decode data read from serial: 194 195.. code-block:: python 196 197 import serial 198 from pw_hdlc import decode 199 200 ser = serial.Serial() 201 decoder = decode.FrameDecoder() 202 203 while True: 204 for frame in decoder.process_valid_frames(ser.read()): 205 # Handle the decoded frame 206 207Additional features 208=================== 209 210pw::stream::SysIoWriter 211------------------------ 212The ``SysIoWriter`` C++ class implements the ``Writer`` interface with 213``pw::sys_io``. This Writer may be used by the C++ encoder to send HDLC frames 214over serial. 215 216HdlcRpcClient 217------------- 218.. autoclass:: pw_hdlc.rpc.HdlcRpcClient 219 :members: 220 221Roadmap 222======= 223- **Expanded protocol support** - ``pw_hdlc`` currently only supports 224 unnumbered information frames. Support for different frame types and 225 extended control fields may be added in the future. 226 227- **Higher performance** - We plan to improve the overall performance of the 228 decoder and encoder implementations by using SIMD/NEON. 229 230Compatibility 231============= 232C++17 233