|
36 | 36 |
|
37 | 37 | //| """Two wire serial protocol target
|
38 | 38 | //|
|
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 |
84 | 233 | //|
|
85 | 234 | //| .. 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. |
88 | 236 | //| Make sure the I2C host supports this.
|
89 | 237 | //|
|
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. |
91 | 239 | //| This can be worked around by using the ``i2c-gpio`` bit banging driver.
|
92 | 240 | //| Since the RPi firmware uses the hw i2c, it's not possible to emulate a HAT eeprom."""
|
93 | 241 |
|
|
0 commit comments