Skip to content

Commit a9d800b

Browse files
committed
docs/i2ctarget: added examples and additional explanation to the I2CTarget() class
Signed-off-by: Andrew Mirsky <[email protected]>
1 parent 781c577 commit a9d800b

File tree

1 file changed

+188
-39
lines changed

1 file changed

+188
-39
lines changed

shared-bindings/i2ctarget/__init__.c

Lines changed: 188 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -36,58 +36,207 @@
3636

3737
//| """Two wire serial protocol target
3838
//|
39-
//| The `i2ctarget` module contains classes to support an I2C target.
39+
//| In many cases, i2c is used by a controller to retrieve (or send) to a peripheral (target). It is also possible
40+
//| for a device to act as a target for another controller. However, a device can only be a controller or a target on
41+
//| an I2C bus (although many devices now support multiple I2C busses).
4042
//|
41-
//| Example emulating a target with 2 addresses (read and write)::
43+
//| .. note::
44+
//| `I2CTarget` takes a list of addresses, but not all devices support this feature
4245
//|
46+
//| Example of emulating a simple device that can only handle single writes and reads::
47+
//|
48+
//|
49+
//| import board
50+
//| from i2ctarget import I2CTarget
51+
//|
52+
//| import adafruit_logging as logging
53+
//| from logging import NamedStreamHandler
54+
//|
55+
//| logger = logging.getLogger('i2ctarget')
56+
//| logger.setLevel(logging.INFO)
57+
//| logger.addHandler(NamedStreamHandler())
58+
//|
59+
//| logger.info("\n\ncode starting...")
60+
//|
61+
//| # initialize an I2C target with a device address of 0x40
62+
//| with I2CTarget(board.SCL, board.SDA, (0x40,)) as device:
63+
//|
64+
//| while True:
65+
//| # check if there's a pending device request
66+
//| i2c_target_request = device.request()
67+
//|
68+
//| if not i2c_target_request:
69+
//| # no request is pending
70+
//| continue
71+
//|
72+
//| # `with` invokes I2CTargetRequest's functions to handle the necessary opening and closing of a transaction
73+
//| with i2c_target_request:
74+
//|
75+
//| # the address associated with the request
76+
//| address = i2c_target_request.address
77+
//|
78+
//| if i2c_target_request.is_read:
79+
//| logger.info(f"read request to address '0x{address:02x}'")
80+
//|
81+
//| # for our emulated device, return a fixed value for the request
82+
//| buffer = bytes([0xaa])
83+
//| i2c_target_request.write(buffer)
84+
//| else:
85+
//| # transaction is a write request
86+
//| data = i2c_target_request.read(1)
87+
//| logger.info(f"write request to address 0x{address:02x}: {data}")
88+
//| # for our emulated device, writes have no effect
89+
//|
90+
//| This example sets up an I2C device that can be accessed via another device running circuitpython::
91+
//| import busio
92+
//| import board
93+
//| i2c = busio.I2C(board.SCL, board.SDA)
94+
//|
95+
//|
96+
//| # perform a single read
97+
//| while not i2c.try_lock():
98+
//| pass
99+
//| buffer = bytearray(1)
100+
//| i2c.readfrom_into(0x40, buffer)
101+
//| print(f"device responded with {buffer}")
102+
//| i2c.unlock()
103+
//|
104+
//| # perform a single write
105+
//| while not i2c.try_lock():
106+
//| pass
107+
//| buffer = bytearray(1)
108+
//| buffer[0] = 0x12
109+
//| i2c.writeto(0x40, buffer)
110+
//| print(f"wrote {buffer} to device")
111+
//| i2c.unlock()
112+
//|
113+
//| Typically, i2c devices support writes and reads to/from multiple register indices::
43114
//| import board
44115
//| from i2ctarget import I2CTarget
45116
//|
117+
//| import adafruit_logging as logging
118+
//| from logging import NamedStreamHandler
119+
//|
120+
//| logger = logging.getLogger('i2ctarget')
121+
//| logger.setLevel(logging.INFO)
122+
//| logger.addHandler(NamedStreamHandler())
123+
//|
124+
//| # emulate a target with 16 registers
46125
//| regs = [0] * 16
47-
//| index = 0
126+
//| register_index = None
127+
//|
128+
//| logger.info("\n\ncode starting...")
129+
//|
130+
//| # initialize an I2C target with a device address of 0x40
131+
//| with I2CTarget(board.SCL, board.SDA, (0x40,)) as device:
48132
//|
49-
//| with I2CTarget(board.SCL, board.SDA, (0x40, 0x41)) as device:
50133
//| while True:
51-
//| r = device.request()
52-
//| if not r:
53-
//| # Maybe do some housekeeping
134+
//| # check if there's a pending device request
135+
//| i2c_target_request = device.request()
136+
//|
137+
//| if not i2c_target_request:
138+
//| # no request is pending
54139
//| continue
55-
//| with r: # Closes the transfer if necessary by sending a NACK or feeding dummy bytes
56-
//| if r.address == 0x40:
57-
//| if not r.is_read: # Main write which is Selected read
58-
//| b = r.read(1)
59-
//| if not b or b[0] > 15:
60-
//| break
61-
//| index = b[0]
62-
//| b = r.read(1)
63-
//| if b:
64-
//| regs[index] = b[0]
65-
//| elif r.is_restart: # Combined transfer: This is the Main read message
66-
//| n = r.write(bytes([regs[index]]))
67-
//| #else:
68-
//| # A read transfer is not supported in this example
69-
//| # If the microcontroller tries, it will get 0xff byte(s) by the ctx manager (r.close())
70-
//| elif r.address == 0x41:
71-
//| if not r.is_read:
72-
//| b = r.read(1)
73-
//| if b and b[0] == 0xde:
74-
//| # do something
75-
//| pass
76-
//|
77-
//| This example sets up an I2C device that can be accessed from Linux like this::
78-
//|
79-
//| $ i2cget -y 1 0x40 0x01
80-
//| 0x00
81-
//| $ i2cset -y 1 0x40 0x01 0xaa
82-
//| $ i2cget -y 1 0x40 0x01
83-
//| 0xaa
140+
//|
141+
//| # work with the i2c request
142+
//| with i2c_target_request:
143+
//|
144+
//| if not i2c_target_request.is_read:
145+
//| # a write request
146+
//|
147+
//| # bytearray contains the request's first byte, the register's index
148+
//| index = i2c_target_request.read(1)[0]
149+
//|
150+
//| # bytearray containing the request's second byte, the data
151+
//| data = i2c_target_request.read(1)
152+
//|
153+
//| # if the request doesn't have a second byte, this is read transaction
154+
//| if not data:
155+
//|
156+
//| # since we're only emulating 16 registers, read from a larger address is an error
157+
//| if index > 15:
158+
//| logger.error(f"write portion of read transaction has invalid index {index}")
159+
//| continue
160+
//|
161+
//| logger.info(f"write portion of read transaction, set index to {index}'")
162+
//| register_index = index
163+
//| continue
164+
//|
165+
//| # since we're only emulating 16 registers, writing to a larger address is an error
166+
//| if index > 15:
167+
//| logger.error(f"write request to incorrect index {index}")
168+
//| continue
169+
//|
170+
//| logger.info(f"write request to index {index}: {data}")
171+
//| regs[index] = data[0]
172+
//| else:
173+
//| # our emulated device requires a read to be part of a full write-then-read transaction
174+
//| if not i2c_target_request.is_restart:
175+
//| logger.warning(f"read request without first writing is not supported")
176+
//| # still need to respond, but result data is not defined
177+
//| i2c_target_request.write(bytes([0xff]))
178+
//| register_index = None
179+
//| continue
180+
//|
181+
//| # the single read transaction case is covered above, so we should always have a valid index
182+
//| assert(register_index is not None)
183+
//|
184+
//| # the write-then-read to an invalid address is covered above,
185+
//| # but if this is a restarted read, index might be out of bounds so need to check
186+
//| if register_index > 16:
187+
//| logger.error(f"restarted read yielded an unsupported index")
188+
//| i2c_target_request.write(bytes([0xff]))
189+
//| register_index = None
190+
//| continue
191+
//|
192+
//| # retrieve the data from our register file and respond
193+
//| data = regs[register_index]
194+
//| logger.info(f"read request from index {register_index}: {data}")
195+
//| i2c_target_request.write(bytes([data]))
196+
//|
197+
//| # in our emulated device, a single read transaction is covered above
198+
//| # so any subsequent restarted read gets the value at the next index
199+
//| assert(i2c_target_request.is_restart is True)
200+
//| register_index += 1
201+
//|
202+
//| This second example creates I2C target device that can be accessed via another device running circuitpython::
203+
//| import busio
204+
//| import board
205+
//| i2c = busio.I2C(board.SCL, board.SDA)
206+
//|
207+
//| # perform a write transaction
208+
//| while not i2c.try_lock():
209+
//| pass
210+
//| buffer = bytearray(2)
211+
//| buffer[0] = 0x0b # the register index
212+
//| buffer[1] = 0xa1 # the value
213+
//| i2c.writeto(0x40, buffer)
214+
//| print(f"wrote {buffer} to device")
215+
//| i2c.unlock()
216+
//|
217+
//| # perform a full read transaction (write-then-read)
218+
//| while not i2c.try_lock():
219+
//| pass
220+
//| index_buffer = bytearray(1)
221+
//| index_buffer[0] = 0x0b
222+
//| read_buffer = bytearray(1)
223+
//| i2c.writeto_then_readfrom(0x40, index_buffer, read_buffer)
224+
//| print(f"read from device index {index_buffer}: {read_buffer}")
225+
//| i2c.unlock()
226+
//|
227+
//| Or accessed from Linux like this::
228+
//|
229+
//| $ i2cget -y 1 0x40 0x0b
230+
//| 0xff
231+
//| $ i2cset -y 1 0x40 0x0b 0xa1
232+
//| $ i2cget -y 1 0x40 0x01
233+
//| 0xa1
84234
//|
85235
//| .. warning::
86-
//| I2CTarget makes use of clock stretching in order to slow down
87-
//| the host.
236+
//| I2CTarget makes use of clock stretching in order to slow down the host.
88237
//| Make sure the I2C host supports this.
89238
//|
90-
//| Raspberry Pi in particular does not support this with its I2C hw block.
239+
//| Raspberry Pi 3 and below, in particular, do not support this with its I2C hw block.
91240
//| This can be worked around by using the ``i2c-gpio`` bit banging driver.
92241
//| Since the RPi firmware uses the hw i2c, it's not possible to emulate a HAT eeprom."""
93242

0 commit comments

Comments
 (0)