From 84bfe4a9287fc21b2073c31f0e7c206d8061fa11 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sun, 9 Nov 2025 12:21:59 -0300 Subject: [PATCH 1/3] add regmap.Device8Txer --- internal/regmap/device8tx.go | 196 +++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 internal/regmap/device8tx.go diff --git a/internal/regmap/device8tx.go b/internal/regmap/device8tx.go new file mode 100644 index 000000000..3ce199d8d --- /dev/null +++ b/internal/regmap/device8tx.go @@ -0,0 +1,196 @@ +// Package regmap provides transaction-based interfaces for reading and writing +// to device registers over I2C and SPI buses with pre-allocated buffers. +package regmap + +import ( + "errors" + + "tinygo.org/x/drivers" +) + +var ( + // errNotInTx indicates an operation was attempted outside of an active transaction. + errNotInTx = errors.New("device not in Tx") + + // errInTx indicates a transaction was started while another is still active. + errInTx = errors.New("device already in Tx") + + // errShortWriteBuffer indicates the write buffer is too small for the requested operation. + errShortWriteBuffer = errors.New("device write buffer too short") + + // errShortReadBuffer indicates the read buffer is too small for the requested operation. + errShortReadBuffer = errors.New("device read buffer too short") +) + +// Device8Txer wraps a Device8 to provide buffered transaction support for +// I2C and SPI operations. It maintains pre-allocated buffers to avoid heap +// allocations during register access operations. +// +// Users must call SetBuffers to configure the write and read buffers before +// initiating transactions. +type Device8Txer struct { + Device8 + writeBuf []byte // Pre-allocated buffer for write operations + readBuf []byte // Pre-allocated buffer for read operations + inTx bool // Tracks whether a transaction is currently active +} + +// SetBuffers configures the write and read buffers for this device. +// These buffers are reused across transactions to avoid heap allocations. +// +// The writebuf should be large enough to hold the register address plus +// all data bytes to be written in a single transaction. +func (d *Device8Txer) SetBuffers(writebuf, readbuf []byte) { + d.readBuf = readbuf + d.writeBuf = writebuf +} + +// Tx8 represents an active transaction for an 8-bit register device. +// It tracks the write buffer and current offset as data is added to the transaction. +// +// Use AddWriteByte or AddWriteData to add data to the transaction, then call +// DoTxI2C or DoTxSPI to execute the transaction over the bus. +type Tx8 struct { + dw *Device8Txer // Reference to the parent device + off int // Current offset in the write buffer +} + +// Tx initiates a new transaction for writing to the specified register address. +// +// Parameters: +// - writeAddr: The 8-bit register address to write to +// +// Returns a Tx8 handle that can be used to add data and execute the transaction. +// +// Returns an error if: +// - A transaction is already active (errInTx) +// - The write buffer is too short (errShortWriteBuffer) +func (dw *Device8Txer) Tx(writeAddr uint8) (Tx8, error) { + if dw.inTx { + return Tx8{}, errInTx + } else if len(dw.writeBuf) < 1 { + return Tx8{}, errShortWriteBuffer + } + dw.writeBuf[0] = writeAddr + return Tx8{dw: dw, off: 1}, nil +} + +// AddWriteData appends multiple bytes to the current transaction's write buffer. +// +// Parameters: +// - buf: Variable number of bytes to add to the transaction +// +// Returns an error if: +// - No transaction is active (errNotInTx) +// - The write buffer doesn't have enough space (errShortWriteBuffer) +func (tx *Tx8) AddWriteData(buf ...byte) error { + if !tx.dw.inTx { + return errNotInTx + } + avail := tx.dw.writeBuf[tx.off:] + if len(avail) < len(buf) { + return errShortWriteBuffer + } + n := copy(avail, buf) + tx.off += n + return nil +} + +// AddWriteByte appends a single byte to the current transaction's write buffer. +// +// Parameters: +// - b: The byte to add to the transaction +// +// Returns an error if: +// - No transaction is active (errNotInTx) +// - The write buffer doesn't have enough space (errShortWriteBuffer) +func (tx *Tx8) AddWriteByte(b byte) error { + if !tx.dw.inTx { + return errNotInTx + } + avail := tx.dw.writeBuf[tx.off:] + if len(avail) < 1 { + return errShortWriteBuffer + } + avail[0] = b + tx.off++ + return nil +} + +// DoTxI2C executes the transaction over an I2C bus. +// +// This performs a combined write-read I2C transaction, first sending the +// register address and any data added to the transaction, then reading +// the specified number of bytes from the device. +// +// Parameters: +// - bus: The I2C bus to communicate over +// - deviceAddr: The I2C address of the target device +// - readLength: Number of bytes to read from the device +// +// Returns the read data as a slice of the internal read buffer, valid until +// the next transaction. The transaction is automatically freed after execution. +// +// Returns an error if: +// - No transaction is active (errNotInTx) +// - The read buffer is too short (errShortReadBuffer) +// - The I2C transaction fails +func (tx *Tx8) DoTxI2C(bus drivers.I2C, deviceAddr uint16, readLength int) ([]byte, error) { + if tx.off == 0 || !tx.dw.inTx { + return nil, errNotInTx + } + defer tx.freeTx() + if len(tx.dw.readBuf) < readLength { + return nil, errShortReadBuffer + } + rbuf := tx.dw.readBuf[:readLength] + err := bus.Tx(deviceAddr, tx.dw.writeBuf[:tx.off], rbuf) + if err != nil { + return nil, err + } + return rbuf, err +} + +// DoTxSPI executes the transaction over an SPI bus. +// +// This performs a full-duplex SPI transaction, simultaneously writing the +// register address and data while reading the same number of bytes from the device. +// +// If no read buffer was configured (readBuf is nil), this performs a write-only +// transaction and returns nil without error. +// +// Parameters: +// - bus: The SPI bus to communicate over +// +// Returns the read data as a slice of the internal read buffer (same length as +// the write data), valid until the next transaction. The transaction is +// automatically freed after execution. +// +// Returns an error if: +// - No transaction is active (errNotInTx) +// - The read buffer is too short (errShortReadBuffer) +// - The SPI transaction fails +func (tx *Tx8) DoTxSPI(bus drivers.SPI) (readBuf []byte, err error) { + if tx.off == 0 || !tx.dw.inTx { + return nil, errNotInTx + } + defer tx.freeTx() + if tx.dw.readBuf == nil { + err = bus.Tx(tx.dw.writeBuf[:tx.off], nil) // Special case, only use write buffer functionality. + return nil, err + } else if len(readBuf) < tx.off { + return nil, errShortReadBuffer + } + rbuf := tx.dw.readBuf[:tx.off] + err = bus.Tx(tx.dw.writeBuf[:tx.off], rbuf) + if err != nil { + return nil, err + } + return rbuf, err +} + +// freeTx marks the transaction as complete, allowing a new transaction to be started. +// This is called internally by DoTxI2C and DoTxSPI after the transaction completes. +func (tx *Tx8) freeTx() { + tx.dw.inTx = false +} From 7267dc1014115309b021ae7764643fd00f3b3ed5 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sun, 9 Nov 2025 12:26:53 -0300 Subject: [PATCH 2/3] change method name --- internal/regmap/device8tx.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/regmap/device8tx.go b/internal/regmap/device8tx.go index 3ce199d8d..433efb8e1 100644 --- a/internal/regmap/device8tx.go +++ b/internal/regmap/device8tx.go @@ -35,12 +35,12 @@ type Device8Txer struct { inTx bool // Tracks whether a transaction is currently active } -// SetBuffers configures the write and read buffers for this device. +// SetTxBuffers configures the write and read buffers for this device. // These buffers are reused across transactions to avoid heap allocations. // // The writebuf should be large enough to hold the register address plus // all data bytes to be written in a single transaction. -func (d *Device8Txer) SetBuffers(writebuf, readbuf []byte) { +func (d *Device8Txer) SetTxBuffers(writebuf, readbuf []byte) { d.readBuf = readbuf d.writeBuf = writebuf } From ec6385c15a85e7f0afcd28b342004d357eb916e8 Mon Sep 17 00:00:00 2001 From: Patricio Whittingslow Date: Sun, 9 Nov 2025 12:35:11 -0300 Subject: [PATCH 3/3] add example usage --- internal/regmap/regmap_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 internal/regmap/regmap_test.go diff --git a/internal/regmap/regmap_test.go b/internal/regmap/regmap_test.go new file mode 100644 index 000000000..9281f7e87 --- /dev/null +++ b/internal/regmap/regmap_test.go @@ -0,0 +1,34 @@ +package regmap + +import ( + "fmt" + + "tinygo.org/x/drivers" +) + +func ExampleDevice8Txer() { + // Initialization. + var dtx Device8Txer + dtx.SetTxBuffers(make([]byte, 256), make([]byte, 256)) + + // Usage. + const ( + defaultAddr = 65 + REG_WRITE = 0x1f + IOCTL_CALL = 0xc0 + ) + tx, err := dtx.Tx(REG_WRITE) + if err != nil { + panic(err) + } + err = tx.AddWriteData(IOCTL_CALL, 0x80, 0x80) + if err != nil { + panic(err) + } + var bus drivers.I2C + readData, err := tx.DoTxI2C(bus, defaultAddr, 20) + if err != nil { + panic(err) + } + fmt.Println(readData) +}