|
45 | 45 | //| |
46 | 46 | //| Example of emulating a simple device that can only handle single writes and reads:: |
47 | 47 | //| |
48 | | -//| |
49 | 48 | //| import board |
50 | 49 | //| from i2ctarget import I2CTarget |
51 | 50 | //| |
|
56 | 55 | //| logger.setLevel(logging.INFO) |
57 | 56 | //| logger.addHandler(NamedStreamHandler()) |
58 | 57 | //| |
59 | | -//| logger.info("\n\ncode starting...") |
| 58 | +//| logger.info("\\n\\ncode starting...") |
60 | 59 | //| |
61 | 60 | //| # initialize an I2C target with a device address of 0x40 |
62 | 61 | //| with I2CTarget(board.SCL, board.SDA, (0x40,)) as device: |
|
69 | 68 | //| # no request is pending |
70 | 69 | //| continue |
71 | 70 | //| |
72 | | -//| # `with` invokes I2CTargetRequest's functions to handle the necessary opening and closing of a transaction |
| 71 | +//| # `with` invokes I2CTargetRequest's functions to handle the necessary opening and closing of a request |
73 | 72 | //| with i2c_target_request: |
74 | 73 | //| |
75 | 74 | //| # the address associated with the request |
|
87 | 86 | //| logger.info(f"write request to address 0x{address:02x}: {data}") |
88 | 87 | //| # for our emulated device, writes have no effect |
89 | 88 | //| |
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:: |
114 | | -//| import board |
115 | | -//| from i2ctarget import I2CTarget |
116 | | -//| |
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 |
125 | | -//| regs = [0] * 16 |
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: |
132 | | -//| |
133 | | -//| while True: |
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 |
139 | | -//| continue |
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:: |
| 89 | +//| This example creates an I2C target device that can be accessed via another device as an I2C controller:: |
| 90 | +//| |
| 91 | +//| import busio |
| 92 | +//| import board |
| 93 | +//| i2c = busio.I2C(board.SCL, board.SDA) |
| 94 | +//| |
| 95 | +//| # perform a single read |
| 96 | +//| while not i2c.try_lock(): |
| 97 | +//| pass |
| 98 | +//| buffer = bytearray(1) |
| 99 | +//| i2c.readfrom_into(0x40, buffer) |
| 100 | +//| print(f"device responded with {buffer}") |
| 101 | +//| i2c.unlock() |
| 102 | +//| |
| 103 | +//| # perform a single write |
| 104 | +//| while not i2c.try_lock(): |
| 105 | +//| pass |
| 106 | +//| buffer = bytearray(1) |
| 107 | +//| buffer[0] = 0x12 |
| 108 | +//| i2c.writeto(0x40, buffer) |
| 109 | +//| print(f"wrote {buffer} to device") |
| 110 | +//| i2c.unlock() |
| 111 | +//| |
| 112 | +//| Typically, i2c devices support writes and reads to/from multiple register indices as in this example :: |
| 113 | +//| |
| 114 | +//| import board |
| 115 | +//| from i2ctarget import I2CTarget |
| 116 | +//| |
| 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 |
| 125 | +//| regs = [0] * 16 |
| 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: |
| 132 | +//| |
| 133 | +//| while True: |
| 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 |
| 139 | +//| continue |
| 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 as an I2C controller:: |
| 203 | +//| |
| 204 | +//| import busio |
| 205 | +//| import board |
| 206 | +//| i2c = busio.I2C(board.SCL, board.SDA) |
| 207 | +//| |
| 208 | +//| # perform a write transaction |
| 209 | +//| while not i2c.try_lock(): |
| 210 | +//| pass |
| 211 | +//| buffer = bytearray(2) |
| 212 | +//| buffer[0] = 0x0b # the register index |
| 213 | +//| buffer[1] = 0xa1 # the value |
| 214 | +//| i2c.writeto(0x40, buffer) |
| 215 | +//| print(f"wrote {buffer} to device") |
| 216 | +//| i2c.unlock() |
| 217 | +//| |
| 218 | +//| # perform a full read transaction (write-then-read) |
| 219 | +//| while not i2c.try_lock(): |
| 220 | +//| pass |
| 221 | +//| index_buffer = bytearray(1) |
| 222 | +//| index_buffer[0] = 0x0b |
| 223 | +//| read_buffer = bytearray(1) |
| 224 | +//| i2c.writeto_then_readfrom(0x40, index_buffer, read_buffer) |
| 225 | +//| print(f"read from device index {index_buffer}: {read_buffer}") |
| 226 | +//| i2c.unlock() |
| 227 | +//| |
| 228 | +//| Or accessed from Linux like this:: |
228 | 229 | //| |
229 | 230 | //| $ i2cget -y 1 0x40 0x0b |
230 | 231 | //| 0xff |
|
0 commit comments