Get started & guides#

pw_hdlc: Simple, robust, and efficient serial communication

pw_hdlc implements a subset of the High-Level Data Link Control (HDLC) protocol. HDLC is a data link layer protocol intended for serial communication between devices and is standardized as ISO/IEC 13239:2002.

Get started with pw_hdlc#

Depend on the library:

Add @pigweed//pw_hdlc to the deps list in your Bazel target:

cc_library("...") {
  # ...
  deps = [
    # ...
    "@pigweed//pw_hdlc",
    # ...
  ]
}

This assumes that your Bazel WORKSPACE has a repository named @pigweed that points to the upstream Pigweed repository.

Add $dir_pw_hdlc to the deps list in your pw_executable() build target:

pw_executable("...") {
  # ...
  deps = [
    # ...
    "$dir_pw_hdlc",
    # ...
  ]
}

Add pw_hdlc to your pw_add_library or similar CMake target:

pw_add_library(my_library STATIC
  HEADERS
    # ...
  PRIVATE_DEPS
    # ...
    pw_hdlc
    # ...
)

There are two ways to use pw_hdlc from a Zephyr project:

  • (Recommended) Depend on pw_hdlc in your CMake target. See the CMake tab. The Pigweed team recommends this approach because it enables precise CMake dependency analysis.

  • Add CONFIG_PIGWEED_HDLC=y to your Zephyr project’s configuration. Also add CONFIG_PIGWEED_HDLC_RPC=y if using RPC. Although this is the typical Zephyr solution, the Pigweed team doesn’t recommend this approach because it makes pw_hdlc a global dependency and exposes its includes to all targets.

And then encode and decode some data!

Set up RPC over HDLC#

See RPC over HDLC example.

Encoding#

// Writes a span of data to a pw::stream::Writer and returns the status. This
// implementation uses the pw_checksum module to compute the CRC-32 frame check
// sequence.

#include "pw_hdlc/encoder.h"
#include "pw_hdlc/sys_io_stream.h"

int main() {
  pw::stream::SysIoWriter serial_writer;
  Status status = pw::hdlc::WriteUIFrame(123 /* address */, data, serial_writer);
  if (!status.ok()) {
    PW_LOG_INFO("Writing frame failed! %s", status.str());
  }
}
# Read bytes from serial and encode HDLC frames

import serial
from pw_hdlc import encode

ser = serial.Serial()
address = 123
ser.write(encode.ui_frame(address, b'your data here!'))

Allocating buffers when encoding#

HDLC encoding overhead changes depending on the payload size and the nature of the data being encoded. pw_hdlc provides helper functions for determining the size of buffers. The helper functions provide worst-case sizes of frames given a certain payload size and vice-versa.

#include "pw_assert/check.h"
#include "pw_bytes/span.h"
#include "pw_hdlc/encoder"
#include "pw_hdlc/encoded_size.h"
#include "pw_status/status.h"

// The max on-the-wire size in bytes of a single HDLC frame after encoding.
constexpr size_t kMtu = 512;
constexpr size_t kRpcEncodeBufferSize = pw::hdlc::MaxSafePayloadSize(kMtu);
std::array<std::byte, kRpcEncodeBufferSize> rpc_encode_buffer;

// Any data encoded to this buffer is guaranteed to fit in the MTU after
// HDLC encoding.
pw::ConstByteSpan GetRpcEncodeBuffer() {
  return rpc_encode_buffer;
}

Decoding#

// Read individual bytes from pw::sys_io and decode HDLC frames.

#include "pw_hdlc/decoder.h"
#include "pw_sys_io/sys_io.h"

int main() {
  std::byte data;
  std::array<std::byte, 128> decode_buffer;
  pw::hdlc::Decoder decoder(decode_buffer);
  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
    }
  }
}
# Decode data read from serial

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

Allocating buffers when decoding#

pw::hdlc::Decoder is a helper for allocating a buffer. It has slightly lower overhead because it doesn’t need to decode the entire escaped frame in-memory.

#include "pw_hdlc/decoder.h"

// The max on-the-wire size in bytes of a single HDLC frame after encoding.
constexpr size_t kMtu = 512;

// Create a decoder given the MTU constraint.
constexpr size_t kDecoderBufferSize =
    pw::hdlc::Decoder::RequiredBufferSizeForFrameSize(kMtu);
pw::hdlc::DecoderBuffer<kDecoderBufferSize> decoder;

More pw_hdlc docs#

Get started & guides

How to set up and use pw_hdlc

API reference

Reference details about the pw_hdlc API

Design

Design details about pw_hdlc

Code size analysis

The code size impact of pw_hdlc

RPC over HDLC example

A step-by-step example of sending RPCs over HDLC

Experimental async router

An experimental asynchronous HDLC router using pw_channel