Skip to content

Commit b340b73

Browse files
authored
Merge pull request #9236 from mirskytech/cpy_9232
add `I2CTarget` documentation, fix problem with I2C restart requests [#9232]
2 parents d857738 + 5cace54 commit b340b73

File tree

2 files changed

+197
-49
lines changed

2 files changed

+197
-49
lines changed

ports/raspberrypi/common-hal/i2ctarget/I2CTarget.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ int common_hal_i2ctarget_i2c_target_is_addressed(i2ctarget_i2c_target_obj_t *sel
111111

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

116116
common_hal_i2ctarget_i2c_target_ack(self, true);
117117
return 1;

shared-bindings/i2ctarget/__init__.c

Lines changed: 196 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -36,58 +36,206 @@
3636

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

0 commit comments

Comments
 (0)