Create your hardware in libsigrok

Step 1: Add your device to Makefile.am

if HW_QUANTR
src_libdrivers_la_SOURCES += \
	src/hardware/quantr/protocol.h \
	src/hardware/quantr/protocol.c \
	src/hardware/quantr/api.c
endif

Step 2: in configure.ac

SR_DRIVER([Quantr], [quantr], [serial_comm])

Step 3: Create /src/hardware/quantr folder

add api.c, protocol.c and protocol.h

api.c:

#include <config.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"
#include "protocol.h"

static const uint32_t scanopts[] = {
    SR_CONF_CONN,
    SR_CONF_SERIALCOMM,
};

static const uint32_t devopts[] = {
    SR_CONF_LOGIC_ANALYZER,
    SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
};

static const uint64_t samplerates[] = {
    SR_KHZ(1),
    SR_KHZ(10),
    SR_KHZ(100),
    SR_MHZ(1),
    SR_MHZ(10),
};

SR_PRIV struct sr_dev_driver quantr_driver_info = {
    .name = "quantr",
    .longname = "Quantr Device",
    .api_version = 1,
    .init = quantr_init,
    .cleanup = quantr_cleanup,
    .scan = quantr_scan,
    .dev_list = quantr_dev_list,
    .dev_clear = quantr_dev_clear,
    .config_get = quantr_config_get,
    .config_set = quantr_config_set,
    .config_list = quantr_config_list,
    .dev_open = quantr_dev_open,
    .dev_close = quantr_dev_close,
    .dev_acquisition_start = quantr_dev_acquisition_start,
    .dev_acquisition_stop = quantr_dev_acquisition_stop,
    .context = NULL,
};

SR_REGISTER_DEV_DRIVER(quantr_driver_info);

protocol.c:

#include "protocol.h"
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"

#define LOG_PREFIX "quantr"

static const uint32_t scanopts[] = {
    SR_CONF_CONN,
    SR_CONF_SERIALCOMM,
};

static const uint32_t devopts[] = {
    SR_CONF_LOGIC_ANALYZER,
    SR_CONF_SAMPLERATE | SR_CONF_GET | SR_CONF_SET | SR_CONF_LIST,
};

static const uint64_t samplerates[] = {
    SR_KHZ(1),
    SR_KHZ(10),
    SR_KHZ(100),
    SR_MHZ(1),
    SR_MHZ(10),
};

/* Initialize the driver */
SR_PRIV int quantr_init(struct sr_dev_driver *di, struct sr_context *sr_ctx)
{
    return std_init(di, sr_ctx);
}

/* Cleanup resources */
SR_PRIV int quantr_cleanup(const struct sr_dev_driver *di)
{
    return std_cleanup(di);
}

/* Scan for devices */
SR_PRIV GSList *quantr_scan(struct sr_dev_driver *di, GSList *options)
{
    GSList *devices = NULL;
    struct sr_dev_inst *sdi;
    struct dev_context *devc;
    struct sr_serial_dev_inst *serial;
    const char *conn;
    const char *serialcomm;

    (void)di;

    /* Parse options */
    conn = NULL;
    serialcomm = "9600/8n1"; /* Default serial settings */
    
    if (sr_serial_extract_options(options, &conn, &serialcomm) != SR_OK)
        return NULL;

    if (!conn)
        return NULL;

    /* Try to open the serial port */
    serial = sr_serial_dev_inst_new(conn, serialcomm);
    if (!serial)
        return NULL;

    if (serial_open(serial, SERIAL_RDWR) != SR_OK) {
        sr_serial_dev_inst_free(serial);
        return NULL;
    }

    /* Try to communicate with the device to verify it's there */
    /* You would typically send an identification command here */
    
    /* Create device instance */
    sdi = g_malloc0(sizeof(struct sr_dev_inst));
    sdi->status = SR_ST_INACTIVE;
    sdi->vendor = g_strdup("Quantr");
    sdi->model = g_strdup("Device");
    sdi->inst_type = SR_INST_SERIAL;
    sdi->conn = serial;
    
    if (!sdi) {
        serial_close(serial);
        sr_serial_dev_inst_free(serial);
        return NULL;
    }

    devc = g_malloc0(sizeof(struct dev_context));
    devc->serial = serial;
    devc->samplerate = 1000000; /* Default 1MHz */
    devc->num_channels = 8; /* Default 8 channels */
    devc->buffer_size = 4096;
    devc->buffer = g_malloc(devc->buffer_size);
    sdi->priv = devc;
    
    serial_close(serial);
    devices = g_slist_append(devices, sdi);
    return devices;
}

/* List devices */
SR_PRIV GSList *quantr_dev_list(const struct sr_dev_driver *di)
{
    return ((struct sr_dev_driver *)di)->context;
}

/* Clear device instances */
SR_PRIV int quantr_dev_clear(const struct sr_dev_driver *di)
{
    return std_dev_clear(di);
}

/* Get configuration */
SR_PRIV int quantr_config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
    struct dev_context *devc = sdi->priv;

    (void)cg;

    switch (key) {
    case SR_CONF_SAMPLERATE:
        *data = g_variant_new_uint64(devc->samplerate);
        return SR_OK;
    default:
        return SR_ERR_NA;
    }
}

/* Set configuration */
SR_PRIV int quantr_config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
    struct dev_context *devc = sdi->priv;

    (void)cg;

    if (sdi->status != SR_ST_ACTIVE)
        return SR_ERR_DEV_CLOSED;

    switch (key) {
    case SR_CONF_SAMPLERATE:
        devc->samplerate = g_variant_get_uint64(data);
        return SR_OK;
    default:
        return SR_ERR_NA;
    }
}

/* List configuration options */
SR_PRIV int quantr_config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg)
{
    (void)sdi;
    (void)cg;

    switch (key) {
    case SR_CONF_SCAN_OPTIONS:
        *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
                scanopts, ARRAY_SIZE(scanopts), sizeof(uint32_t));
        return SR_OK;
    case SR_CONF_DEVICE_OPTIONS:
        *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
                devopts, ARRAY_SIZE(devopts), sizeof(uint32_t));
        return SR_OK;
    case SR_CONF_SAMPLERATE:
        *data = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT64,
                samplerates, ARRAY_SIZE(samplerates), sizeof(uint64_t));
        return SR_OK;
    default:
        return SR_ERR_NA;
    }
}

/* Open device */
SR_PRIV int quantr_dev_open(struct sr_dev_inst *sdi)
{
    struct dev_context *devc = sdi->priv;
    int ret;

    ret = serial_open(devc->serial, SERIAL_RDWR);
    if (ret != SR_OK)
        return ret;

    sdi->status = SR_ST_ACTIVE;
    return SR_OK;
}

/* Close device */
SR_PRIV int quantr_dev_close(struct sr_dev_inst *sdi)
{
    struct dev_context *devc = sdi->priv;

    if (devc->serial) {
        serial_close(devc->serial);
    }
    sdi->status = SR_ST_INACTIVE;
    return SR_OK;
}

/* Start acquisition */
SR_PRIV int quantr_dev_acquisition_start(const struct sr_dev_inst *sdi)
{
    struct dev_context *devc = sdi->priv;
    int ret;

    if (!devc->serial)
        return SR_ERR;

    /* Send start command to device via serial port */
    ret = serial_write_blocking(devc->serial, "START\n", 6, 1000);
    if (ret < 0)
        return SR_ERR;

    devc->acquisition_running = 1;
    
    /* Set up polling or event-driven data reading here */
    /* For now, this is just a skeleton */
    
    return SR_OK;
}

/* Stop acquisition */
SR_PRIV int quantr_dev_acquisition_stop(struct sr_dev_inst *sdi)
{
    struct dev_context *devc = sdi->priv;
    int ret;

    if (!devc->serial || !devc->acquisition_running)
        return SR_OK;

    /* Send stop command to device via serial port */
    ret = serial_write_blocking(devc->serial, "STOP\n", 5, 1000);
    if (ret < 0)
        return SR_ERR;

    devc->acquisition_running = 0;
    return SR_OK;
}

protocol.h:

#ifndef LIBSIGROK_HARDWARE_QUANTR_PROTOCOL_H
#define LIBSIGROK_HARDWARE_QUANTR_PROTOCOL_H

#include <stdint.h>
#include <libsigrok/libsigrok.h>
#include "libsigrok-internal.h"

/* Device-specific context */
struct dev_context {
    struct sr_serial_dev_inst *serial;
    uint64_t samplerate;
    int num_channels;
    uint8_t *buffer;
    size_t buffer_size;
    int acquisition_running;
};

/* Function prototypes */
SR_PRIV int quantr_init(struct sr_dev_driver *di, struct sr_context *sr_ctx);
SR_PRIV int quantr_cleanup(const struct sr_dev_driver *di);
SR_PRIV GSList *quantr_scan(struct sr_dev_driver *di, GSList *options);
SR_PRIV GSList *quantr_dev_list(const struct sr_dev_driver *di);
SR_PRIV int quantr_dev_clear(const struct sr_dev_driver *di);
SR_PRIV int quantr_config_get(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
SR_PRIV int quantr_config_set(uint32_t key, GVariant *data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
SR_PRIV int quantr_config_list(uint32_t key, GVariant **data, const struct sr_dev_inst *sdi, const struct sr_channel_group *cg);
SR_PRIV int quantr_dev_open(struct sr_dev_inst *sdi);
SR_PRIV int quantr_dev_close(struct sr_dev_inst *sdi);
SR_PRIV int quantr_dev_acquisition_start(const struct sr_dev_inst *sdi);
SR_PRIV int quantr_dev_acquisition_stop(struct sr_dev_inst *sdi);

#endif

Step 4: Build libsigrok

./autogen.sh
./configure
make -j
sudo make install

Step 5: Build sigrok-cli

./autogen.sh
./configure
make -j
sudo make install

Step 6: Test

sigrok-cli --driver quantr:conn=/dev/tty.usbmodem144401 --scan

sigrok-cli --driver quantr:conn=/dev/tty.usbmodem144401:serialcomm=2000000/8n1 --scan

sigrok-cli --driver quantr:conn=/dev/tty.usbmodem3788388D30321:serialcomm=2000000/8n1 --channels CH0=Vcc,CH1=CS,CH2=MISO,CH3=MOSI,CH4=CLK --output-format binary --config samplerate=50m --continuous
sigrok-cli --driver quantr:conn=/dev/tty.usbmodem3864356A32321 --samples 100 --config samplerate=5000 -O ascii:width=100:charset='_"\/'
sigrok-cli --driver quantr:conn=/dev/tty.usbmodem3864356A32321 --samples 100 --config samplerate=5000 -O bits:width=100

Specific channels

sudo sigrok-cli -d quantr::conn=/dev/tty.usbmodem3864356A32321 -P quantr-sigrok-decoder:freq=100k:data0=CH16:data1=CH17:data2=CH18:data3=CH19:data4=CH20:data5=CH21:data6=CH22:data7=CH23 --samples 100 --config samplerate=100kHz -O bits:width=100 -A quantr-sigrok-decoder=hex