Skip to content

TSB – Tiny Serial Bus

TSB is a Go library for communicating with embedded systems over serial connections (UART/serial port) or TCP networks. It provides a unified protocol framework for addressing hardware interfaces such as I2C, UART, SPI, and GPIO ports.

Module: github.com/traulfs/tsb
Go:     >= 1.20

Table of Contents

  1. Protocol Architecture
  2. Server – Connection Setup
  3. I2C
  4. UART
  5. GPIO Port
  6. Modbus
  7. Core API (tsb.go)

Protocol Architecture

Every TSB packet consists of four fields:

Field Length Meaning
Channel variable Routing address; intermediate bytes have bit 7 set
Type variable Protocol type (0x00–0x7F)
Payload 0–250 bytes Payload data
CRC-16 2 bytes Checksum over Channel + Type + Payload (little-endian)

The fully encoded packet is framed with COBS (Consistent Overhead Byte Stuffing) and terminated with a 0x00 byte.

TsbData

type TsbData struct {
    Ch      []byte // Channel
    Typ     []byte // Type
    Payload []byte // Payload (max. 250 bytes)
}

Server – Connection Setup

A Server manages up to 8 jacks (independent device connectors) and routes incoming packets based on jack number and protocol type.

Constructors

// Serial connection
server, err := tsb.NewSerialServer("/dev/ttyUSB0", 115200)

// TCP connection
server, err := tsb.NewTcpServer("localhost:3001")

Methods

server.Close()

// Register a callback for a specific jack and protocol type
server.SetCallback(jack byte, typ byte, f func(*TsbData))

Jacks

Each jack has 128 type-indexed byte channels. Incoming bytes are automatically delivered to the matching channel or passed to a registered callback.

Constant Value Meaning
MaxJacks 8 Maximum number of jacks
JackModeReg 0x80 Mode register
JackUartReg 0x82 UART register
JackPortReg 0x86 Port register
JackI2cReg 0x88 I2C register
Jack Mode Value
JackPort 1
JackI2c 2
JackUart 3
JackSpi 4

I2C

Constructor

i2c, err := tsb.NewI2c(adr uint8, jack byte, server *Server) (*I2C, error)

Sets the jack mode to I2C and initializes the slave address.

Setting the Address

err = i2c.SetAdr(adr byte) error

Raw Data Transfer

n, err := i2c.Write(buf []byte) (int, error)  // max. 127 bytes
n, err := i2c.Read(buf []byte)  (int, error)  // max. 127 bytes

Register Operations

Method Description
ReadRegU8(reg) (byte, error) Read an 8-bit value
WriteRegU8(reg, value) error Write an 8-bit value
ReadRegBytes(reg, n) ([]byte, int, error) Read n bytes starting at register
ReadRegU16BE(reg) (uint16, error) Read 16-bit big-endian
WriteRegU16BE(reg, value) error Write 16-bit big-endian
ReadRegU16LE(reg) (uint16, error) Read 16-bit little-endian
WriteRegU16LE(reg, value) error Write 16-bit little-endian
ReadRegS16BE(reg) (int16, error) Read 16-bit signed BE
WriteRegS16BE(reg, value) error Write 16-bit signed BE
ReadRegS16LE(reg) (int16, error) Read 16-bit signed LE
WriteRegS16LE(reg, value) error Write 16-bit signed LE

Example

server, _ := tsb.NewSerialServer("/dev/ttyUSB0", 115200)
defer server.Close()

i2c, _ := tsb.NewI2c(0x50, 5, server)

val, err := i2c.ReadRegU8(0x20)
err = i2c.WriteRegU16BE(0x24, 0x1234)
data, _, err := i2c.ReadRegBytes(0x30, 2)

UART

Constructor

uart, err := tsb.NewUart(jack byte, server *Server) (*UART, error)

Configuration

err = uart.Config(rs485, baud, databits, parity, stopbits uint16) error

The parameters are combined with a bitwise OR:

Baud rate:

Constant Value
UartBaudAuto 0
UartBaud9600
UartBaud115200
UartBaud230400
UartBaud460800
UartBaud921600
UartBaud1000000
UartBaud3000000
(more)

Stop bits:

Constant Value
UartStopbits1 0x0000
UartStopbits15 0x0100
UartStopbits2 0x0200

Parity:

Constant Value
UartParityNone 0x0000
UartParityEven 0x0400
UartParityOdd 0x0800

Data bits:

Constant Value
UartData8 0x0000
UartData7 0x2000
UartData6 0x3000
UartData5 0x4000
UartData9 0x1000

RS485:

Constant Value
UartRS485 0x8000

Data Transfer

n, err := uart.Write(b []byte) (int, error)  // non-blocking
n, err := uart.Read(b []byte)  (int, error)  // blocks until first byte

Example

uart, _ := tsb.NewUart(5, server)
uart.Config(0, tsb.UartBaud115200, tsb.UartData8, tsb.UartParityNone, tsb.UartStopbits1)

uart.Write([]byte("Hello\n"))

buf := make([]byte, 256)
n, _ := uart.Read(buf)
fmt.Printf("Received: %s\n", buf[:n])

GPIO Port

Constructor

port, err := tsb.NewPort(jack byte, server *Server) (*Port, error)

Pads

Constant Value Meaning
PortPad0 1 GPIO pad 0
PortPad1 2 GPIO pad 1
PortPad2 4 GPIO pad 2
PortPad3 8 GPIO pad 3
PortAllPads 15 All pads 0–3

Port Commands

Constant Value Action
PortcharReadWrite 0x00 Read/write
PortcharRead 0x01 Read
PortcharSetOutput 0x02 Set output high
PortcharClearOutput 0x03 Set output low
PortcharToggleOutput 0x04 Toggle output
PortcharNotification 0x05 Enable notification
PortcharDelay 0x06 Delay
PortcharSetDirection 0x08 Configure as output
PortcharClearDirection 0x09 Configure as input
PortcharSetPullEnable 0x0A Enable pull-up
PortcharClearPullEnable 0x0B Disable pull-up
PortcharSetNotification 0x0C Enable pad notification
PortcharClearNotification 0x0D Disable pad notification
PortcharSetLED 0x10 Set LED
PortcharClearLED 0x11 Clear LED
PortcharToggleLED 0x12 Toggle LED

Helper Function

cmd := tsb.PortCharNibble(code byte, value int) []byte

Encodes port commands with a nibble value into the wire format.

Data Transfer

n, err := port.Write(b []byte) (int, error)
n, err := port.Read(b []byte)  (int, error)

Example

port, _ := tsb.NewPort(1, server)

// Configure pads 0+1 as inputs with notification
port.Write(tsb.PortCharNibble(tsb.PortcharClearDirection, 0x03))
port.Write(tsb.PortCharNibble(tsb.PortcharSetNotification, 0x03))

// Toggle LED on pad 0
port.Write(tsb.PortCharNibble(tsb.PortcharToggleLED, 1))

// Read notifications
buf := make([]byte, 256)
n, _ := port.Read(buf)

Modbus

Modbus is used internally to configure hardware registers of the jacks.

Function

err = tsb.ModbusWriteSingleRegister(adr uint16, jack byte, server *Server, value uint16) error

Function Codes

Constant Value Meaning
MbFcReadHoldingRegister 0x03 Read holding register
MbFcWriteSingleRegister 0x06 Write single register
MbFcWriteMultipleRegister 0x10 Write multiple registers

Register Addresses

Constant Address Meaning
ModeRegisterAdr 0x0002 Jack mode
PortRegisterAdr 0x0004 GPIO configuration
UartRegisterAdr 0x0006 UART configuration
I2cRegisterAdr 0x0008 I2C configuration
SpiRegisterAdr 0x000A SPI configuration

Core API (tsb.go)

Encoding/Decoding

// Convert a channel string ("3.4.5") into a byte array
b := tsb.Channel2Bytes(ch string) []byte

// Encode TsbData into wire format (Ch + Typ + Payload + CRC16)
raw := tsb.Encode(td TsbData) []byte

// Decode wire format into TsbData (verifies CRC)
td, err := tsb.Decode(packet []byte) (TsbData, error)

// COBS encoding
encoded := tsb.CobsEncode(p []byte) []byte
decoded, err := tsb.CobsDecode(b []byte) ([]byte, error)

Goroutines for Data Streams

// Background goroutine: reads COBS packets from io.Reader, delivers TsbData
dataCh, doneCh := tsb.GetData(r io.Reader, chanLen int) (chan TsbData, chan struct{})

// Background goroutine: encodes TsbData and writes to io.Writer
sendCh := tsb.PutData(w io.Writer, chanLen int) chan TsbData

Debug Flags

tsb.Verbose      = true  // Protocol trace
tsb.ErrorVerbose = true  // Extended error output

Protocol Types

Constant Value Usage
TypRaw 0x01 Raw data (UART, SPI)
TypText 0x02 Text data
TypPort 0x03 GPIO port
TypI2c 0x04 I2C
TypSpi 0x05 SPI
TypModbus 0x07 Modbus
TypAtCmd 0x09 AT commands
TypCoap 0x21 CoAP
TypCbor 0x31 CBOR
TypCan 0x41 CAN bus
TypInflux 0x75 InfluxDB
TypLog 0x7D Log
TypWarning 0x7E Warning
TypError 0x7F Error

Limits

Constant Value Meaning
Buflen 1000 Buffer length
MaxTyp 127 Maximum type value
MaxPayload 250 Maximum payload size