I2C

Open an I2C bus, exchange raw bytes, use register helpers, and manage the live handle safely.


I2C is the usual path to small sensors, displays, and converters. Frothy keeps the bus itself explicit: open it once, pass its Handle to each transfer, and close it when the project no longer needs it.

Read A Sensor Register

The following shape works for devices with an 8-bit identity register. Replace the address and register with values from the device datasheet.

bus is i2c.open: 0, $sda, $scl, 400000
identity is i2c.read-reg: bus, 104, 117
i2c.close: bus
set bus to nil

104 is the decimal form of 7-bit address 0x68; 117 is register 0x75. Frothy integer literals are decimal, so convert hexadecimal datasheet values before entering them.

Word Table

WordResultUse
i2c.openHandleOpen a port on selected SDA/SCL pins and frequency
i2c.writenilSend Text or Bytes to a 7-bit address
i2c.readBytesRead a transient byte buffer
i2c.read-regIntRead one 8-bit register
i2c.write-regnilWrite one 8-bit register
i2c.read-reg16IntRead one big-endian 16-bit register
i2c.write-reg16nilWrite one big-endian 16-bit register
i2c.closenilClose the bus and release its handle

Open And Close

bus is i2c.open: 0, $sda, $scl, 100000
-- transfers go here
i2c.close: bus
set bus to nil

The board constants $sda and $scl select the default pins. Use numeric pin values when a circuit is wired elsewhere. Common frequencies are 100000 and 400000 Hz, subject to the devices, wiring, pull-ups, and target support.

i2c.open returns a live runtime Handle, not an integer port number. A closed or stale handle cannot be reused.

Raw Transfers

i2c.write accepts Text or Bytes, so short fixed command sequences can be written directly:

i2c.write: bus, 104, "AT"

to inspect-reply [
  here reply is i2c.read: bus, 104, 2
  bytes.at: reply, 0
  bytes.at: reply, 1
]

inspect-reply:

i2c.read returns transient Bytes. Copy a reading into persistent Text before the current evaluation ends or when the value must survive a save:

saved-reply is text.pack: (i2c.read: bus, 104, 2)

The count, address, register, and register values must fit their respective byte or 16-bit ranges. A device NACK, invalid address, unavailable port, or bus failure is reported as an error.

Register Helpers

The register words perform the common write-register-then-read transaction:

i2c.write-reg: bus, 104, 107, 0
who-am-i is i2c.read-reg: bus, 104, 117

i2c.write-reg16: bus, 72, 1, 32768
config is i2c.read-reg16: bus, 72, 1

The 16-bit helpers use big-endian byte order. If a component uses little-endian values, multi-byte register addresses, repeated-start rules outside this contract, or a larger transaction, compose it with raw i2c.write and i2c.read instead.

Persistence Pattern

Handles are volatile and make save fail while stored in a project slot. Keep the durable binding non-handle, open the bus at boot, and explicitly release it before saving:

bus is false

to sensor.open [
  set bus to i2c.open: 0, $sda, $scl, 400000
]

to sensor.close [
  i2c.close: bus
  set bus to false
]

boot is fn [ sensor.open: ]

See Persist the Recipe, Not the Handle for the generalized close/save/reopen lifecycle and the important save commit-boundary behavior.

See Text, Bytes & PAD for byte lifetimes and Board constants & helpers for the named pins.