You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
6.9 KiB
233 lines
6.9 KiB
.. _module-pw_hdlc:
|
|
|
|
-------
|
|
pw_hdlc
|
|
-------
|
|
`High-Level Data Link Control (HDLC)
|
|
<https://en.wikipedia.org/wiki/High-Level_Data_Link_Control>`_ is a data link
|
|
layer protocol intended for serial communication between devices. HDLC is
|
|
standardized as `ISO/IEC 13239:2002 <https://www.iso.org/standard/37010.html>`_.
|
|
|
|
The ``pw_hdlc`` module provides a simple, robust frame-oriented transport that
|
|
uses a subset of the HDLC protocol. ``pw_hdlc`` supports sending between
|
|
embedded devices or the host. It can be used with :ref:`module-pw_rpc` to enable
|
|
remote procedure calls (RPCs) on embedded on devices.
|
|
|
|
**Why use the pw_hdlc module?**
|
|
|
|
* Enables the transmission of RPCs and other data between devices over serial.
|
|
* Detects corruption and data loss.
|
|
* Light-weight, simple, and easy to use.
|
|
* Supports streaming to transport without buffering, since the length is not
|
|
encoded.
|
|
|
|
.. admonition:: Try it out!
|
|
|
|
For an example of how to use HDLC with :ref:`module-pw_rpc`, see the
|
|
:ref:`module-pw_hdlc-rpc-example`.
|
|
|
|
.. toctree::
|
|
:maxdepth: 1
|
|
:hidden:
|
|
|
|
rpc_example/docs
|
|
|
|
Protocol Description
|
|
====================
|
|
|
|
Frames
|
|
------
|
|
The HDLC implementation in ``pw_hdlc`` supports only HDLC unnumbered
|
|
information frames. These frames are encoded as follows:
|
|
|
|
.. code-block:: text
|
|
|
|
_________________________________________
|
|
| | | | | | |...
|
|
| | | | | | |... [More frames]
|
|
|_|_|_|__________________________|____|_|...
|
|
F A C Payload FCS F
|
|
|
|
F = flag byte (0x7e, the ~ character)
|
|
A = address field
|
|
C = control field
|
|
FCS = frame check sequence (CRC-32)
|
|
|
|
|
|
Encoding and sending data
|
|
-------------------------
|
|
This module first writes an initial frame delimiter byte (0x7E) to indicate the
|
|
beginning of the frame. Before sending any of the payload data through serial,
|
|
the special bytes are escaped:
|
|
|
|
+-------------------------+-----------------------+
|
|
| Unescaped Special Bytes | Escaped Special Bytes |
|
|
+=========================+=======================+
|
|
| 7E | 7D 5E |
|
|
+-------------------------+-----------------------+
|
|
| 7D | 7D 5D |
|
|
+-------------------------+-----------------------+
|
|
|
|
The bytes of the payload are escaped and written in a single pass. The
|
|
frame check sequence is calculated, escaped, and written after. After this, a
|
|
final frame delimiter byte (0x7E) is written to mark the end of the frame.
|
|
|
|
Decoding received bytes
|
|
-----------------------
|
|
Frames may be received in multiple parts, so we need to store the received data
|
|
in a buffer until the ending frame delimiter (0x7E) is read. When the
|
|
``pw_hdlc`` decoder receives data, it unescapes it and adds it to a buffer.
|
|
When the frame is complete, it calculates and verifies the frame check sequence
|
|
and does the following:
|
|
|
|
* If correctly verified, the decoder returns the decoded frame.
|
|
* If the checksum verification fails, the frame is discarded and an error is
|
|
reported.
|
|
|
|
API Usage
|
|
=========
|
|
There are two primary functions of the ``pw_hdlc`` module:
|
|
|
|
* **Encoding** data by constructing a frame with the escaped payload bytes and
|
|
frame check sequence.
|
|
* **Decoding** data by unescaping the received bytes, verifying the frame
|
|
check sequence, and returning successfully decoded frames.
|
|
|
|
Encoder
|
|
-------
|
|
The Encoder API provides a single function that encodes data as an HDLC
|
|
unnumbered information frame.
|
|
|
|
C++
|
|
^^^
|
|
.. cpp:namespace:: pw
|
|
|
|
.. cpp:function:: Status hdlc::WriteUIFrame(uint64_t address, ConstByteSpan data, stream::Writer& writer)
|
|
|
|
Writes a span of data to a :ref:`pw::stream::Writer <module-pw_stream>` and
|
|
returns the status. This implementation uses the :ref:`module-pw_checksum`
|
|
module to compute the CRC-32 frame check sequence.
|
|
|
|
.. code-block:: cpp
|
|
|
|
#include "pw_hdlc/encoder.h"
|
|
#include "pw_hdlc/sys_io_stream.h"
|
|
|
|
int main() {
|
|
pw::stream::SysIoWriter serial_writer;
|
|
Status status = WriteUIFrame(123 /* address */,
|
|
data,
|
|
serial_writer);
|
|
if (!status.ok()) {
|
|
PW_LOG_INFO("Writing frame failed! %s", status.str());
|
|
}
|
|
}
|
|
|
|
Python
|
|
^^^^^^
|
|
.. automodule:: pw_hdlc.encode
|
|
:members:
|
|
|
|
.. code-block:: python
|
|
|
|
import serial
|
|
from pw_hdlc import encode
|
|
|
|
ser = serial.Serial()
|
|
ser.write(encode.ui_frame(b'your data here!'))
|
|
|
|
Decoder
|
|
-------
|
|
The decoder class unescapes received bytes and adds them to a buffer. Complete,
|
|
valid HDLC frames are yielded as they are received.
|
|
|
|
C++
|
|
^^^
|
|
.. cpp:class:: pw::hdlc::Decoder
|
|
|
|
.. cpp:function:: pw::Result<Frame> Process(std::byte b)
|
|
|
|
Parses a single byte of an HDLC stream. Returns a Result with the complete
|
|
frame if the byte completes a frame. The status is the following:
|
|
|
|
- OK - A frame was successfully decoded. The Result contains the Frame,
|
|
which is invalidated by the next Process call.
|
|
- UNAVAILABLE - No frame is available.
|
|
- RESOURCE_EXHAUSTED - A frame completed, but it was too large to fit in
|
|
the decoder's buffer.
|
|
- DATA_LOSS - A frame completed, but it was invalid. The frame was
|
|
incomplete or the frame check sequence verification failed.
|
|
|
|
.. cpp:function:: void Process(pw::ConstByteSpan data, F&& callback, Args&&... args)
|
|
|
|
Processes a span of data and calls the provided callback with each frame or
|
|
error.
|
|
|
|
This example demonstrates reading individual bytes from ``pw::sys_io`` and
|
|
decoding HDLC frames:
|
|
|
|
.. code-block:: cpp
|
|
|
|
#include "pw_hdlc/decoder.h"
|
|
#include "pw_sys_io/sys_io.h"
|
|
|
|
int main() {
|
|
std::byte data;
|
|
while (true) {
|
|
if (!pw::sys_io::ReadByte(&data).ok()) {
|
|
// Log serial reading error
|
|
}
|
|
Result<Frame> decoded_frame = decoder.Process(data);
|
|
|
|
if (decoded_frame.ok()) {
|
|
// Handle the decoded frame
|
|
}
|
|
}
|
|
}
|
|
|
|
Python
|
|
^^^^^^
|
|
.. autoclass:: pw_hdlc.decode.FrameDecoder
|
|
:members:
|
|
|
|
Below is an example using the decoder class to decode data read from serial:
|
|
|
|
.. code-block:: python
|
|
|
|
import serial
|
|
from pw_hdlc import decode
|
|
|
|
ser = serial.Serial()
|
|
decoder = decode.FrameDecoder()
|
|
|
|
while True:
|
|
for frame in decoder.process_valid_frames(ser.read()):
|
|
# Handle the decoded frame
|
|
|
|
Additional features
|
|
===================
|
|
|
|
pw::stream::SysIoWriter
|
|
------------------------
|
|
The ``SysIoWriter`` C++ class implements the ``Writer`` interface with
|
|
``pw::sys_io``. This Writer may be used by the C++ encoder to send HDLC frames
|
|
over serial.
|
|
|
|
HdlcRpcClient
|
|
-------------
|
|
.. autoclass:: pw_hdlc.rpc.HdlcRpcClient
|
|
:members:
|
|
|
|
Roadmap
|
|
=======
|
|
- **Expanded protocol support** - ``pw_hdlc`` currently only supports
|
|
unnumbered information frames. Support for different frame types and
|
|
extended control fields may be added in the future.
|
|
|
|
- **Higher performance** - We plan to improve the overall performance of the
|
|
decoder and encoder implementations by using SIMD/NEON.
|
|
|
|
Compatibility
|
|
=============
|
|
C++17
|