Skip to content

Instantly share code, notes, and snippets.

@max-dark
Last active March 3, 2025 14:54
Show Gist options
  • Save max-dark/d4acb76e3dc702325336e3084f11fed1 to your computer and use it in GitHub Desktop.
Save max-dark/d4acb76e3dc702325336e3084f11fed1 to your computer and use it in GitHub Desktop.
draft for SUMP protocol parser
// See: Openbench Logic Sniffer (OLS) / SUMP protocol
// https://sigrok.org/wiki/SUMP_compatibles
// https://sigrok.org/wiki/Openbench_Logic_Sniffer
// https://sigrok.org/gitweb/?p=libsigrok.git;a=blob_plain;f=src/hardware/openbench-logic-sniffer/protocol.h;hb=HEAD
// https://firmware.buspirate.com/binmode-reference/protocol-sump
// https://web.archive.org/web/20190317154112if_/http://mygizmos.org/ols/Logic-Sniffer-FPGA-Spec.pdf
#include <QCoreApplication>
#include <QDebug>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInfo() << "port dump";
QSerialPort port;
port.setPortName("/dev/ttyUSB1");
//port.setBaudRate(QSerialPort::Baud115200);
port.setBaudRate(921600);
port.setDataBits(QSerialPort::Data8);
port.setStopBits(QSerialPort::OneStop);
port.setParity(QSerialPort::NoParity);
bool run = false;
uint8_t state = 0;
QTimer send_data;
QObject::connect(&send_data, &QTimer::timeout, [&]() {
if (!run) return;
port.write((const char*)&state, sizeof(state));
++state;
});
QByteArray data;
QObject::connect(&port, &QSerialPort::readyRead, [&]() {
auto tmp = port.readAll();
qInfo() << "inp:" << tmp.toHex();
data.append(tmp);
constexpr int long_cmd = 5;
bool have_data = !data.isEmpty();
while (have_data)
{
int to_remove = 1;
bool can_process_long = data.size() >= long_cmd;
auto bytes = reinterpret_cast<const uint8_t*>(data.data());
switch (bytes[0])
{
case 0x00:
{
/*
* Reset (00h)
* Resets the device.
* Should be sent 5 times when the receiver status is unknown.
* (It could be waiting for up to four bytes of pending long command data.)
*/
qInfo() << "reset";
run = false;
state = 0;
send_data.stop();
break;
}
case 0x01:
{
/*
* Run (01h)
* Arms the trigger.
*/
qInfo() << "run";
run = true;
state = 0;
send_data.setInterval(std::chrono::milliseconds{10});
send_data.start();
break;
}
case 0x02:
{
/*
* D (02h)
* Asks for device identification.
* The device will respond with four bytes.
* The first three ("SLA") identify the device.
* The last one identifies the protocol version which is currently either "0" or "1"
*
* SigRok accepts "1SLO" and "1ALS"
*/
qInfo() << "get id";
port.write("1ALS", 4); // note: FOURID("SLA1") / LSB first
break;
}
case 0x04: // EXT_GET_METADATA
{
// https://sigrok.org/gitweb/?p=libsigrok.git;a=blob;f=src/hardware/openbench-logic-sniffer/protocol.h;h=652ee8d1848c1ec9e8a94b15dd224616f6b2b4eb;hb=HEAD#l59
/* Metadata tokens */
#define METADATA_TOKEN_END 0x0
#define METADATA_TOKEN_DEVICE_NAME 0x1
#define METADATA_TOKEN_FPGA_VERSION 0x2
#define METADATA_TOKEN_ANCILLARY_VERSION 0x3
#define METADATA_TOKEN_NUM_PROBES_LONG 0x20
#define METADATA_TOKEN_SAMPLE_MEMORY_BYTES 0x21
#define METADATA_TOKEN_DYNAMIC_MEMORY_BYTES 0x22
#define METADATA_TOKEN_MAX_SAMPLE_RATE_HZ 0x23
#define METADATA_TOKEN_PROTOCOL_VERSION_LONG 0x24
#define METADATA_TOKEN_CAPABILITIES 0x25 /* not implemented in Demon Core v3.07 */
#define METADATA_TOKEN_NUM_PROBES_SHORT 0x40
#define METADATA_TOKEN_PROTOCOL_VERSION_SHORT 0x41
qInfo() << "EXT: get metadata";
const char info[] =
// METADATA_TOKEN_DEVICE_NAME
"\x01PortDump Emulator\x00" // device name
// METADATA_TOKEN_FPGA_VERSION
"\x02No FPGA\x00" // FPGA firmware version
// METADATA_TOKEN_ANCILLARY_VERSION
"\x03No MCU \x00" // MCU firmware version
// METADATA_TOKEN_NUM_PROBES_LONG
"\x20\x00\x00\x00\x08" // (u32, MSB first) num probes(channels) == 8x
// METADATA_TOKEN_SAMPLE_MEMORY_BYTES
"\x21\x00\xA0\x00\x00" // (u32, MSB first) (wrong meaning?)amount of sample memory in bytes == 10MiB
// METADATA_TOKEN_DYNAMIC_MEMORY_BYTES
"\x22\x00\x02\x00\x00" // (u32, MSB first) (unused?) amount of dynamic memory in bytes == 64KiB
// METADATA_TOKEN_MAX_SAMPLE_RATE_HZ
"\x23\x00\x0F\x42\x40" // (u32, MSB first) (ignored?)Maximum sample rate (Hz) = 1MHz
// METADATA_TOKEN_PROTOCOL_VERSION_LONG
"\x24\x00\x00\x00\x02" // (u32, MSB first) (ignored?)protocol version == 2
// METADATA_TOKEN_CAPABILITIES
//"\x25\x00\x00\x00\x00" // (u32, MSB first) (unused?) Capabilities
// METADATA_TOKEN_NUM_PROBES_SHORT
//"\x40\x08" // short: num channels == 8x channels
// METADATA_TOKEN_PROTOCOL_VERSION_SHORT
//"\x41\x02" // short: protocol version == 2
// METADATA_TOKEN_END
"" // End of data == 0x00
;
qInfo() << "EXT: send" << port.write(info, sizeof(info));
break;
}
case 0x11:
{
/*
* XON (11h)
* Put transmitter out of pause mode.
* It will continue to transmit captured data if any is pending.
* This command is being used for xon/xoff flow control.
*/
qInfo() << "XON - continue";
break;
}
case 0x13:
{
/*
* XOFF (13h)
* Put transmitter in pause mode.
* It will stop transmitting captured data.
* This command is being used for xon/xoff flow control.
*/
qInfo() << "XOFF - pause";
break;
}
case 0xC0: case 0xC4: case 0xC8: case 0xCC:
{
to_remove = long_cmd;
if (!can_process_long)
{
break;
}
/*
* Set Trigger Mask (C0h, C4h, C8h, CCh)
* Defines which trigger values must match.
* In parallel mode each bit represents one channel,
* in serial mode each bit represents one of the last 32 samples of the selected channel.
* The opcodes refer to stage 0-3 in the order given above.
* (Protocol version 0 only supports stage 0.)
*/
qInfo() << "trigger mask" << data.left(long_cmd).toHex();
break;
}
case 0xC1: case 0xC5: case 0xC9: case 0xCD:
{
to_remove = long_cmd;
if (!can_process_long)
{
break;
}
/*
* Set Trigger Values (C1h, C5h, C9h, CDh)
* Defines which values individual bits must have.
* In parallel mode each bit represents one channel,
* in serial mode each bit represents one of the last 32 samples of the selected channel.
* The opcodes refer to stage 0-3 in the order given above.
* (Protocol version 0 only supports stage 0.)
*/
qInfo() << "trigger value" << data.left(long_cmd).toHex();
break;
}
case 0xC2: case 0xC6: case 0xCA: case 0xCE:
{
to_remove = long_cmd;
if (!can_process_long)
{
break;
}
/*
* Set Trigger Configuration (C2h, C6h, CAh, CEh)
* Configures the selected trigger stage.
* The opcodes refer to stage 0-3 in the order given above.
* The following parameters will be set:
* * delay - If a match occures, the action of the stage is delayed by the given
* number of samples.
* * level - Trigger level at which the stage becomes active.
* * channel - Channel to be used in serial mode. (0-31 in normal operation;
* 0-15 when demux flag is set)
* * serial - When set to 1 the stage operates as serial trigger,
* otherwise it used as parallel trigger.
* * start - When set to 1 a match will start the capturing process.
* The trigger level will rise on match regardless of this flag.
*/
uint16_t delay = bytes[1] | (bytes[2] << 8);
uint16_t level = bytes[3] & 0b0000'0011;
uint16_t channel = ((bytes[3] & 0b1111'0000) >> 4) | ((bytes[4] & 0b0000'0001) << 4);
bool b_start = (bytes[4] & 0b0000'1000) != 0;
bool b_serial = (bytes[4] & 0b0000'0100) != 0;
qInfo() << "trigger config" << data.left(long_cmd).toHex();
qInfo() << " delay =" << delay;
qInfo() << " level =" << level;
qInfo() << " channel =" << channel;
qInfo() << " b_start =" << b_start;
qInfo() << " b_serial=" << b_serial;
break;
}
case 0x80:
{
to_remove = long_cmd;
if (!can_process_long)
{
break;
}
/*
* Set Divider (80h)
* When x is written, the sampling frequency is set to f = clock / (x + 1)
*/
uint32_t divider = 0
| (bytes[1] << 0)
| (bytes[2] << 8)
| (bytes[3] << 16)
;
qInfo() << "set divider = " << divider << "/" << data.left(long_cmd).toHex();
break;
}
case 0x81:
{
to_remove = long_cmd;
if (!can_process_long)
{
break;
}
/*
* Set Read & Delay Count (81h)
* Read Count is the number of samples (divided by four) to read back from memory
* and sent to the host computer.
* Delay Count is the number of samples (divided by four) to capture after the trigger fired.
* A Read Count bigger than the Delay Count means that data from before
* the trigger match will be read back.
* This data will only be valid if the device was running long enough before
* the trigger matched.
*
* SigRok uses 0x83 / 0x84 if devc->max_samples > (256 * 1024)
* See also: 0x83 / 0x84
*/
uint16_t read_cnt = bytes[1] | (bytes[2] << 8);
uint16_t delay_cnt = bytes[3] | (bytes[4] << 8);
qInfo() << "set read & delay count" << data.left(long_cmd).toHex();
qInfo() << " read :" << read_cnt;
qInfo() << " delay:" << delay_cnt;
break;
}
case 0x83: // see 0x81
{
to_remove = long_cmd;
if (!can_process_long)
{
break;
}
uint32_t count = 0
| (bytes[1] << 0)
| (bytes[2] << 8)
| (bytes[3] << 16)
| (bytes[4] << 24)
;
qInfo() << "EXT: set delay count = " << count << data.left(long_cmd).toHex();
break;
}
case 0x84: // see 0x81
{
to_remove = long_cmd;
if (!can_process_long)
{
break;
}
uint32_t count = 0
| (bytes[1] << 0)
| (bytes[2] << 8)
| (bytes[3] << 16)
| (bytes[4] << 24)
;
qInfo() << "EXT: set read count = " << count << data.left(long_cmd).toHex();
break;
}
case 0x82:
{
to_remove = long_cmd;
if (!can_process_long)
{
break;
}
/*
* Set Flags (82h)
* Sets the following flags:
*
* * demux Enables the demux input module. (Filter must be off.)
* Meaning: Demux Mode. Double-Data-Rate Capturing.
* Captures groups 0 & 1 inputs on both edges of internal
* reference clock (ie: two samples per clock per enabled group).
* * filter Enables the filter input module. (Demux must be off.)
* Meaning: Noise Filter Mode. Reduces occurrence of glitches on inputs
* * channel groups Disable channel group.
* Disabled groups are excluded from data transmissions.
* This can be used to speed up transfers.
* There are four groups, each represented by one bit.
* Starting with the least significant bit of the channel group field
* channels are assigned as follows: 0-7, 8-15, 16-23, 24-31
* * external Selects the clock to be used for sampling.
* External Clock Source. Use external clock for sampling.
* If set to 0, the internal clock divided by the configured divider is used,
* and if set to 1, the external clock will be used.
* (filter and demux are only available with internal clock)
* * inverted When set to 1, the external clock will be inverted before being used.
* Inverted External Capture Clock.
* Capture data on falling edge of sample clock when using external clock.
* The inversion causes a delay that may cause problems at very high clock rates.
* This option only has an effect with external set to 1.
*/
uint8_t flags = bytes[1];
bool b_demux = 0 != (flags & 0b0000'0001);
bool b_filter = 0 != (flags & 0b0000'0010);
uint32_t groups = (flags & 0b00111100) >> 2; // disabled groups mask
bool b_external = 0 != (flags & 0b0100'0000);
bool b_inverted = 0 != (flags & 0b1000'0000);
qInfo() << "set flags" << data.left(long_cmd).toHex() << Qt::bin << flags;
qInfo() << " b_demux" << b_demux;
qInfo() << " b_filter" << b_filter;
qInfo() << " groups" << Qt::bin << groups;
qInfo() << " b_external" << b_external;
qInfo() << " b_inverted" << b_inverted;
break;
}
default:
{
qInfo() << "Unknown (skip all):" << data.toHex();
to_remove = data.size();
break;
}
}
bool removed = false; // BUG here(?): no enough data
if (to_remove <= data.size())
{
data.remove(0, to_remove);
removed = true;
}
have_data = removed && !data.isEmpty();
}
});
qInfo() << "Open:" << port.open(QIODevice::ReadWrite);
return QCoreApplication::exec();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment