Skip to content

Commit

Permalink
Merge pull request #9236 from mirskytech/cpy_9232
Browse files Browse the repository at this point in the history
add `I2CTarget` documentation, fix problem with I2C restart requests [#9232]
  • Loading branch information
tannewt committed May 13, 2024
2 parents d857738 + 5cace54 commit b340b73
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 49 deletions.
2 changes: 1 addition & 1 deletion ports/raspberrypi/common-hal/i2ctarget/I2CTarget.c
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ int common_hal_i2ctarget_i2c_target_is_addressed(i2ctarget_i2c_target_obj_t *sel

*address = self->peripheral->hw->sar;
*is_read = !(self->peripheral->hw->raw_intr_stat & I2C_IC_INTR_STAT_R_RX_FULL_BITS);
*is_restart = ((self->peripheral->hw->raw_intr_stat & I2C_IC_RAW_INTR_STAT_RD_REQ_RESET) != 0);
*is_restart = ((self->peripheral->hw->raw_intr_stat & I2C_IC_INTR_STAT_R_RESTART_DET_BITS) != 0);

common_hal_i2ctarget_i2c_target_ack(self, true);
return 1;
Expand Down
244 changes: 196 additions & 48 deletions shared-bindings/i2ctarget/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,58 +36,206 @@

//| """Two wire serial protocol target
//|
//| The `i2ctarget` module contains classes to support an I2C target.
//|
//| Example emulating a target with 2 addresses (read and write)::
//|
//| import board
//| from i2ctarget import I2CTarget
//|
//| regs = [0] * 16
//| index = 0
//|
//| with I2CTarget(board.SCL, board.SDA, (0x40, 0x41)) as device:
//| while True:
//| r = device.request()
//| if not r:
//| # Maybe do some housekeeping
//| continue
//| with r: # Closes the transfer if necessary by sending a NACK or feeding dummy bytes
//| if r.address == 0x40:
//| if not r.is_read: # Main write which is Selected read
//| b = r.read(1)
//| if not b or b[0] > 15:
//| break
//| index = b[0]
//| b = r.read(1)
//| if b:
//| regs[index] = b[0]
//| elif r.is_restart: # Combined transfer: This is the Main read message
//| n = r.write(bytes([regs[index]]))
//| #else:
//| # A read transfer is not supported in this example
//| # If the microcontroller tries, it will get 0xff byte(s) by the ctx manager (r.close())
//| elif r.address == 0x41:
//| if not r.is_read:
//| b = r.read(1)
//| if b and b[0] == 0xde:
//| # do something
//| pass
//|
//| This example sets up an I2C device that can be accessed from Linux like this::
//|
//| $ i2cget -y 1 0x40 0x01
//| 0x00
//| $ i2cset -y 1 0x40 0x01 0xaa
//| $ i2cget -y 1 0x40 0x01
//| 0xaa
//| In many cases, i2c is used by a controller to retrieve (or send) to a peripheral (target). It is also possible
//| for a device to act as a target for another controller. However, a device can only be a controller or a target on
//| an I2C bus (although many devices now support multiple I2C busses).
//|
//| .. note::
//| `I2CTarget` takes a list of addresses, but not all devices support this feature
//|
//| Example of emulating a simple device that can only handle single writes and reads::
//|
//| import board
//| from i2ctarget import I2CTarget
//|
//| import adafruit_logging as logging
//|
//| logger = logging.getLogger('i2ctarget')
//| logger.setLevel(logging.INFO)
//| logger.addHandler(logging.StreamHandler())
//|
//| logger.info("\\n\\ncode starting...")
//|
//| # initialize an I2C target with a device address of 0x40
//| with I2CTarget(board.SCL, board.SDA, (0x40,)) as device:
//|
//| while True:
//| # check if there's a pending device request
//| i2c_target_request = device.request()
//|
//| if not i2c_target_request:
//| # no request is pending
//| continue
//|
//| # `with` invokes I2CTargetRequest's functions to handle the necessary opening and closing of a request
//| with i2c_target_request:
//|
//| # the address associated with the request
//| address = i2c_target_request.address
//|
//| if i2c_target_request.is_read:
//| logger.info(f"read request to address '0x{address:02x}'")
//|
//| # for our emulated device, return a fixed value for the request
//| buffer = bytes([0xaa])
//| i2c_target_request.write(buffer)
//| else:
//| # transaction is a write request
//| data = i2c_target_request.read(1)
//| logger.info(f"write request to address 0x{address:02x}: {data}")
//| # for our emulated device, writes have no effect
//|
//| This example creates an I2C target device that can be accessed via another device as an I2C controller::
//|
//| import busio
//| import board
//| i2c = busio.I2C(board.SCL, board.SDA)
//|
//| # perform a single read
//| while not i2c.try_lock():
//| pass
//| buffer = bytearray(1)
//| i2c.readfrom_into(0x40, buffer)
//| print(f"device responded with {buffer}")
//| i2c.unlock()
//|
//| # perform a single write
//| while not i2c.try_lock():
//| pass
//| buffer = bytearray(1)
//| buffer[0] = 0x12
//| i2c.writeto(0x40, buffer)
//| print(f"wrote {buffer} to device")
//| i2c.unlock()
//|
//| Typically, i2c devices support writes and reads to/from multiple register indices as in this example ::
//|
//| import board
//| from i2ctarget import I2CTarget
//|
//| import adafruit_logging as logging
//|
//| logger = logging.getLogger('i2ctarget')
//| logger.setLevel(logging.INFO)
//| logger.addHandler(logging.StreamHandler())
//|
//| # emulate a target with 16 registers
//| regs = [0] * 16
//| register_index = None
//|
//| logger.info("\\n\\ncode starting...")
//|
//| # initialize an I2C target with a device address of 0x40
//| with I2CTarget(board.SCL, board.SDA, (0x40,)) as device:
//|
//| while True:
//| # check if there's a pending device request
//| i2c_target_request = device.request()
//|
//| if not i2c_target_request:
//| # no request is pending
//| continue
//|
//| # work with the i2c request
//| with i2c_target_request:
//|
//| if not i2c_target_request.is_read:
//| # a write request
//|
//| # bytearray contains the request's first byte, the register's index
//| index = i2c_target_request.read(1)[0]
//|
//| # bytearray containing the request's second byte, the data
//| data = i2c_target_request.read(1)
//|
//| # if the request doesn't have a second byte, this is read transaction
//| if not data:
//|
//| # since we're only emulating 16 registers, read from a larger address is an error
//| if index > 15:
//| logger.error(f"write portion of read transaction has invalid index {index}")
//| continue
//|
//| logger.info(f"write portion of read transaction, set index to {index}'")
//| register_index = index
//| continue
//|
//| # since we're only emulating 16 registers, writing to a larger address is an error
//| if index > 15:
//| logger.error(f"write request to incorrect index {index}")
//| continue
//|
//| logger.info(f"write request to index {index}: {data}")
//| regs[index] = data[0]
//| else:
//| # our emulated device requires a read to be part of a full write-then-read transaction
//| if not i2c_target_request.is_restart:
//| logger.warning(f"read request without first writing is not supported")
//| # still need to respond, but result data is not defined
//| i2c_target_request.write(bytes([0xff]))
//| register_index = None
//| continue
//|
//| # the single read transaction case is covered above, so we should always have a valid index
//| assert(register_index is not None)
//|
//| # the write-then-read to an invalid address is covered above,
//| # but if this is a restarted read, index might be out of bounds so need to check
//| if register_index > 16:
//| logger.error(f"restarted read yielded an unsupported index")
//| i2c_target_request.write(bytes([0xff]))
//| register_index = None
//| continue
//|
//| # retrieve the data from our register file and respond
//| data = regs[register_index]
//| logger.info(f"read request from index {register_index}: {data}")
//| i2c_target_request.write(bytes([data]))
//|
//| # in our emulated device, a single read transaction is covered above
//| # so any subsequent restarted read gets the value at the next index
//| assert(i2c_target_request.is_restart is True)
//| register_index += 1
//|
//| This second example creates I2C target device that can be accessed via another device as an I2C controller::
//|
//| import busio
//| import board
//| i2c = busio.I2C(board.SCL, board.SDA)
//|
//| # perform a write transaction
//| while not i2c.try_lock():
//| pass
//| buffer = bytearray(2)
//| buffer[0] = 0x0b # the register index
//| buffer[1] = 0xa1 # the value
//| i2c.writeto(0x40, buffer)
//| print(f"wrote {buffer} to device")
//| i2c.unlock()
//|
//| # perform a full read transaction (write-then-read)
//| while not i2c.try_lock():
//| pass
//| index_buffer = bytearray(1)
//| index_buffer[0] = 0x0b
//| read_buffer = bytearray(1)
//| i2c.writeto_then_readfrom(0x40, index_buffer, read_buffer)
//| print(f"read from device index {index_buffer}: {read_buffer}")
//| i2c.unlock()
//|
//| Or accessed from Linux like this::
//|
//| $ i2cget -y 1 0x40 0x0b
//| 0xff
//| $ i2cset -y 1 0x40 0x0b 0xa1
//| $ i2cget -y 1 0x40 0x01
//| 0xa1
//|
//| .. warning::
//| I2CTarget makes use of clock stretching in order to slow down
//| the host.
//| I2CTarget makes use of clock stretching in order to slow down the host.
//| Make sure the I2C host supports this.
//|
//| Raspberry Pi in particular does not support this with its I2C hw block.
//| Raspberry Pi 3 and below, in particular, do not support this with its I2C hw block.
//| This can be worked around by using the ``i2c-gpio`` bit banging driver.
//| Since the RPi firmware uses the hw i2c, it's not possible to emulate a HAT eeprom."""

Expand Down

0 comments on commit b340b73

Please sign in to comment.