Skip to content

bang-olufsen/hdlcpp

Repository files navigation

hdlcpp

Github Build codecov License

Hdlcpp is a header-only C++11 framing protocol optimized for embedded communication. It uses the HDLC asynchronous framing format for handling data integrity and retransmissions. Hdlcpp is the successor of the yahdlc implementation written in C.

Usage

Hdlcpp requires that a transport read and write function is supplied as e.g. a lambda expression for the actual data transport. Hereby Hdlcpp can easily be integrated e.g. with different UART implementations. It requires a read and write buffer. The buffer type must to compatible with std::span, basically any contiguous sequence containers. The buffers can be size independent for encoding/decoding data, write timeout and number of write retries are also configurable when constructing the instance.

hdlcpp = std::make_shared<Hdlcpp::Hdlcpp>(
    [this](std::span<uint8_t> buffer) { return serial->read(buffer); },
    [this](const std::span<const uint8_t> buffer) { return serial->write(buffer); },
    readBuffer, writeBuffer, writeTimeout, writeRetries);

In the case where the underlying transport layer does not support std::span, the pointer to the first element and the size can be extracted from the span like so.

hdlcpp = std::make_shared<Hdlcpp::Hdlcpp>(
    [this](std::span<uint8_t> buffer) { return serial->read(buffer.data(), buffer.size()); },
    [this](const std::span<const uint8_t> buffer) { return serial->write(buffer.data(), buffer.size()); },
    readBuffer, writeBuffer, writeTimeout, writeRetries);

To read and write data using Hdlcpp the read and write functions are used. These could again e.g. be used as lambdas expressions to a protocol implementation (layered architecture). The protocol could e.g. be nanopb.

Hdlcpp::TransportAddress address { 0x00 };
protocol = std::make_shared<Protocol>(
    [this, address](std::span<uint8_t> buffer) { 
        const auto res = hdlcpp->read(buffer);
        if (res.address == address || res.address == Hdlcpp::BroadcastAddress)
            return res.size;
        return 0;
    },
    [this](const std::span<const uint8_t> buffer) { return hdlcpp->write(address, buffer); });

Python binding

A python binding made using pybind11 can be found under the python folder which can be used e.g. for automated testing.

HDLC implementation

The supported HDLC frames are limited to DATA (I-frame with Poll bit), ACK (S-frame Receive Ready with Final bit) and NACK (S-frame Reject with Final bit). All DATA frames are acknowledged or negative acknowledged. The Address and Control fields uses the 8-bit format which means that the highest sequence number is 7. The FCS field is 16-bit.

Acknowledge of frame Negative acknowledge of frame Acknowledge of frame lost

Limitations

Frame addressing: Addresses are limited to 8bit resolution. Also using ControlEscape (0x7D) or FlagSequence (0x7E) value as address will not work (should be fixed in the future).

Build and run unit tests

The build setup is configured to be run from the given Docker Container. Tested with a PC running 64 bit Ubuntu. WSL will also work.

  • Clone this repository.
  • Install Docker: sudo apt install docker.io. For WSL follow this guide.
  • Run ./build.sh -h to see build options.

NOTE: If using remote-containers for ie. VSCode you can open the folder in the container automatically (see .devcontainer).