std/twi — I²C / TWI master

import std/twi

Two-Wire Interface (TWI, Atmel’s I²C) master primitives. The module exposes the low-level building blocks of an I²C transaction — start, address/data write, acknowledged and unacknowledged reads, and stop — so you can drive any I²C device. Each call busy-waits on the TWI hardware until the operation completes.

Choosing the bit-rate value

@twi_init takes the TWBR register value, not a clock frequency. That is why the examples pass a number like 72 rather than 100000. With the TWI prescaler left at 1, the SCL clock is:

\[f_{SCL} = \frac{F_{CPU}}{16 + 2 \times \text{TWBR}}\]

Solving for TWBR: TWBR = (F_CPU / f_SCL - 16) / 2. For 100 kHz on a 16 MHz clock that is (16000000 / 100000 - 16) / 2 = 72.

SCL frequency

TWBR at 8 MHz

TWBR at 16 MHz

100 kHz (standard)

32

72

400 kHz (fast)

2

12

Standard-mode (100 kHz) is the safe default; many devices also accept fast-mode (400 kHz). TWBR must stay ≥ 10 for reliable master operation, so very high SCL rates need the larger clock.

TWI0

@twi_init($twbr_val: u8)

Initialise the TWI peripheral as a master. $twbr_val is the bit-rate register value that, together with the prescaler, sets the SCL clock frequency.

@twi_start()

Transmit a START condition and wait for it to complete. Begins a transaction.

@twi_stop()

Transmit a STOP condition, releasing the bus and ending the transaction.

@twi_write($data: u8)

Write one byte $data onto the bus and wait for the transfer to finish. Used both for the slave address+R/W byte after a START and for data bytes.

@twi_read_ack() -> u8

Read one byte from the slave and return an ACK to it (signalling “send more”). Returns the received byte.

@twi_read_nack() -> u8

Read one byte from the slave and return a NACK (signalling “this is the last byte”). Returns the received byte; follow with @twi_stop.

@_twi_wait()

Internal: block until the TWI hardware sets its interrupt flag, marking the current operation complete.

Additional TWI instance

Devices with a second TWI peripheral provide the same API on the twi1 module: @twi1_init, @twi1_start, @twi1_stop, @twi1_write, @twi1_read_ack, and @twi1_read_nack.

Example

Read one byte from register 0x00 of an I²C device at 7-bit address 0x68:

target atmega328p
import std/twi

@main {
    @twi_init(72)                  # ~100 kHz SCL at 16 MHz (bit-rate register value)

    @twi_start()                   # START: take the bus
    @twi_write(0x68 * 2)           # slave address 0x68 + write bit (R/W = 0)
    @twi_write(0x00)               # point the slave at register 0x00
    @twi_start()                   # repeated START to switch direction
    @twi_write(0x68 * 2 + 1)       # same address + read bit (R/W = 1)
    ram imut $value: u8 = @twi_read_nack()  # read one byte, NACK = "last one"
    @twi_stop()                    # STOP: release the bus
}