0
# Communication Protocols
1
2
Hardware and software implementations of I2C, SPI, and UART communication protocols with automatic resource locking and CircuitPython compatibility. Provides unified interfaces across diverse hardware platforms with automatic platform detection and driver loading.
3
4
## Capabilities
5
6
### I2C Communication
7
8
The I2C class provides Inter-Integrated Circuit (I2C) communication with automatic platform detection and resource management. Supports both hardware and software implementations depending on the platform.
9
10
```python { .api }
11
class I2C(Lockable):
12
def __init__(self, scl, sda, frequency: int = 100000):
13
"""
14
Initialize I2C bus.
15
16
Args:
17
scl: SCL (clock) pin object from board module
18
sda: SDA (data) pin object from board module
19
frequency: Bus frequency in Hz (default 100000, ignored on Linux)
20
21
Raises:
22
ValueError: If no hardware I2C available on specified pins
23
"""
24
25
def deinit(self) -> None:
26
"""Release I2C bus resources"""
27
28
def scan(self) -> list[int]:
29
"""
30
Scan for I2C devices on the bus.
31
32
Returns:
33
list[int]: List of 7-bit device addresses found on the bus
34
"""
35
36
def readfrom_into(self, address: int, buffer: bytearray, *, start: int = 0, end: int = None) -> None:
37
"""
38
Read from I2C device into existing buffer.
39
40
Args:
41
address: 7-bit I2C device address
42
buffer: Buffer to read data into
43
start: Starting index in buffer (default 0)
44
end: Ending index in buffer (default None for full buffer)
45
"""
46
47
def writeto(self, address: int, buffer: bytes, *, start: int = 0, end: int = None) -> int:
48
"""
49
Write data to I2C device.
50
51
Args:
52
address: 7-bit I2C device address
53
buffer: Data to write (bytes or str)
54
start: Starting index in buffer (default 0)
55
end: Ending index in buffer (default None for full buffer)
56
57
Returns:
58
int: Number of bytes written
59
"""
60
61
def writeto_then_readfrom(
62
self,
63
address: int,
64
buffer_out: bytes,
65
buffer_in: bytearray,
66
*,
67
out_start: int = 0,
68
out_end: int = None,
69
in_start: int = 0,
70
in_end: int = None,
71
stop: bool = False
72
) -> None:
73
"""
74
Write to device then read from device (common for register reads).
75
76
Args:
77
address: 7-bit I2C device address
78
buffer_out: Data to write first
79
buffer_in: Buffer to read response into
80
out_start: Start index for output buffer
81
out_end: End index for output buffer
82
in_start: Start index for input buffer
83
in_end: End index for input buffer
84
stop: Send stop condition between write and read
85
"""
86
```
87
88
### SPI Communication
89
90
The SPI class provides Serial Peripheral Interface (SPI) communication with configurable parameters and automatic platform detection. Includes resource locking for thread safety.
91
92
```python { .api }
93
class SPI(Lockable):
94
def __init__(self, clock, MOSI=None, MISO=None):
95
"""
96
Initialize SPI bus.
97
98
Args:
99
clock: SCK (clock) pin object from board module (required)
100
MOSI: MOSI (Master Out Slave In) pin object (optional for read-only)
101
MISO: MISO (Master In Slave Out) pin object (optional for write-only)
102
103
Raises:
104
ValueError: If no hardware SPI available on specified pins
105
"""
106
107
def configure(self, baudrate: int = 100000, polarity: int = 0, phase: int = 0, bits: int = 8) -> None:
108
"""
109
Configure SPI parameters. Must be called after try_lock().
110
111
Args:
112
baudrate: Clock frequency in Hz (default 100000)
113
polarity: Clock polarity - 0 (idle low) or 1 (idle high)
114
phase: Clock phase - 0 (sample on leading edge) or 1 (trailing edge)
115
bits: Data word size in bits (default 8)
116
117
Raises:
118
RuntimeError: If SPI bus is not locked
119
"""
120
121
def deinit(self) -> None:
122
"""Release SPI bus resources"""
123
124
@property
125
def frequency(self) -> int:
126
"""
127
Current SPI frequency in Hz.
128
129
Returns:
130
int: Current baudrate
131
132
Raises:
133
NotImplementedError: If not supported on platform
134
"""
135
136
def write(self, buf: bytes, start: int = 0, end: int = None) -> None:
137
"""
138
Write data to SPI bus.
139
140
Args:
141
buf: Data to write
142
start: Starting index in buffer (default 0)
143
end: Ending index in buffer (default None for full buffer)
144
"""
145
146
def readinto(self, buf: bytearray, start: int = 0, end: int = None, write_value: int = 0) -> None:
147
"""
148
Read data from SPI bus into buffer.
149
150
Args:
151
buf: Buffer to read data into
152
start: Starting index in buffer (default 0)
153
end: Ending index in buffer (default None for full buffer)
154
write_value: Value to write during read (default 0)
155
"""
156
157
def write_readinto(
158
self,
159
buffer_out: bytes,
160
buffer_in: bytearray,
161
out_start: int = 0,
162
out_end: int = None,
163
in_start: int = 0,
164
in_end: int = None
165
) -> None:
166
"""
167
Simultaneously write and read data (full-duplex).
168
169
Args:
170
buffer_out: Data to write
171
buffer_in: Buffer to read response into
172
out_start: Start index for output buffer
173
out_end: End index for output buffer
174
in_start: Start index for input buffer
175
in_end: End index for input buffer
176
"""
177
```
178
179
### UART Communication
180
181
The UART class provides Universal Asynchronous Receiver-Transmitter (UART) serial communication. Not supported on Linux platforms - use pyserial instead.
182
183
```python { .api }
184
class UART(Lockable):
185
class Parity:
186
ODD: Parity # Odd parity checking
187
EVEN: Parity # Even parity checking
188
189
def __init__(
190
self,
191
tx,
192
rx,
193
baudrate: int = 9600,
194
bits: int = 8,
195
parity = None,
196
stop: int = 1,
197
timeout: int = 1000,
198
receiver_buffer_size: int = 64,
199
flow = None
200
):
201
"""
202
Initialize UART interface.
203
204
Args:
205
tx: TX (transmit) pin object from board module
206
rx: RX (receive) pin object from board module
207
baudrate: Communication speed in baud (default 9600)
208
bits: Data bits per character (default 8)
209
parity: Parity checking (None, UART.Parity.ODD, UART.Parity.EVEN)
210
stop: Stop bits (default 1)
211
timeout: Read timeout in milliseconds (default 1000)
212
receiver_buffer_size: RX buffer size in bytes (default 64)
213
flow: Flow control (not implemented, raises NotImplementedError)
214
215
Raises:
216
RuntimeError: On Linux platforms (use pyserial instead)
217
ValueError: If no hardware UART available on specified pins
218
NotImplementedError: If flow control is specified
219
"""
220
221
def deinit(self) -> None:
222
"""Release UART resources"""
223
224
def read(self, nbytes: int = None) -> bytes:
225
"""
226
Read data from UART.
227
228
Args:
229
nbytes: Number of bytes to read (None for all available)
230
231
Returns:
232
bytes: Data read from UART
233
"""
234
235
def readinto(self, buf: bytearray, nbytes: int = None) -> int:
236
"""
237
Read data from UART into buffer.
238
239
Args:
240
buf: Buffer to read data into
241
nbytes: Number of bytes to read (None for buffer size)
242
243
Returns:
244
int: Number of bytes actually read
245
"""
246
247
def readline(self) -> bytes:
248
"""
249
Read a line of characters up to newline.
250
251
Returns:
252
bytes: Line data including newline character
253
"""
254
255
def write(self, buf: bytes) -> int:
256
"""
257
Write data to UART.
258
259
Args:
260
buf: Data to write
261
262
Returns:
263
int: Number of bytes written
264
"""
265
```
266
267
## Usage Examples
268
269
### I2C Device Communication
270
271
```python
272
import board
273
import busio
274
import time
275
276
# Initialize I2C bus
277
i2c = busio.I2C(board.SCL, board.SDA)
278
279
# Scan for devices
280
with i2c:
281
devices = i2c.scan()
282
print(f"I2C devices found: {[hex(d) for d in devices]}")
283
284
# Communicate with device at address 0x48
285
device_addr = 0x48
286
if device_addr in devices:
287
with i2c:
288
# Write register address
289
i2c.writeto(device_addr, bytes([0x00]))
290
291
# Read response
292
buffer = bytearray(2)
293
i2c.readfrom_into(device_addr, buffer)
294
print(f"Device response: {buffer}")
295
296
# Combined write-then-read operation
297
with i2c:
298
result = bytearray(2)
299
i2c.writeto_then_readfrom(device_addr, bytes([0x01]), result)
300
print(f"Register 0x01 value: {result}")
301
302
# Cleanup
303
i2c.deinit()
304
```
305
306
### SPI Device Communication
307
308
```python
309
import board
310
import busio
311
import digitalio
312
313
# Initialize SPI bus
314
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
315
316
# Setup chip select pin
317
cs = digitalio.DigitalInOut(board.D5)
318
cs.direction = digitalio.Direction.OUTPUT
319
cs.value = True
320
321
# Configure and use SPI
322
while not spi.try_lock():
323
pass
324
325
try:
326
spi.configure(baudrate=1000000, polarity=0, phase=0)
327
328
# Write data
329
cs.value = False
330
spi.write(bytes([0x01, 0x02, 0x03]))
331
cs.value = True
332
333
time.sleep(0.001)
334
335
# Read data
336
cs.value = False
337
result = bytearray(3)
338
spi.readinto(result)
339
cs.value = True
340
341
print(f"SPI response: {result}")
342
343
finally:
344
spi.unlock()
345
346
# Cleanup
347
spi.deinit()
348
cs.deinit()
349
```
350
351
### UART Serial Communication
352
353
```python
354
# Note: UART not supported on Linux - use pyserial library instead
355
import board
356
import busio
357
358
try:
359
# Initialize UART (MicroPython/embedded platforms only)
360
uart = busio.UART(board.TX, board.RX, baudrate=115200)
361
362
# Send data
363
uart.write(b"Hello, UART!\n")
364
365
# Read response
366
data = uart.read(10)
367
if data:
368
print(f"Received: {data}")
369
370
# Read line
371
line = uart.readline()
372
if line:
373
print(f"Line: {line}")
374
375
# Cleanup
376
uart.deinit()
377
378
except RuntimeError as e:
379
print(f"UART not supported: {e}")
380
print("Use pyserial library for serial communication on Linux")
381
```
382
383
### Resource Management
384
385
```python
386
import board
387
import busio
388
389
# Using context managers for automatic cleanup
390
with busio.I2C(board.SCL, board.SDA) as i2c:
391
devices = i2c.scan()
392
print(f"Found devices: {devices}")
393
394
# Manual locking for SPI
395
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
396
397
# Lock before configuration
398
if spi.try_lock():
399
try:
400
spi.configure(baudrate=500000)
401
spi.write(b"test data")
402
finally:
403
spi.unlock()
404
else:
405
print("Could not acquire SPI lock")
406
407
spi.deinit()
408
```
409
410
## Platform Considerations
411
412
### I2C Support
413
- **Hardware I2C**: Most embedded Linux systems, RP2040, specialized adapters
414
- **Software I2C**: Available via bitbangio for platforms without hardware support
415
- **Frequency**: Linux ignores frequency parameter, uses system defaults
416
417
### SPI Support
418
- **Full-duplex**: Most platforms support simultaneous read/write operations
419
- **Pin flexibility**: MOSI/MISO pins optional for unidirectional communication
420
- **Locking required**: Must call try_lock() before configure() and operations
421
422
### UART Limitations
423
- **Linux**: Not supported - use pyserial library instead
424
- **MicroPython**: Full support with hardware flow control limitations
425
- **Embedded platforms**: Variable feature support depending on hardware
426
427
### Error Handling
428
429
```python
430
import board
431
import busio
432
433
try:
434
i2c = busio.I2C(board.SCL, board.SDA)
435
# Use I2C...
436
i2c.deinit()
437
except ValueError as e:
438
print(f"Hardware not available: {e}")
439
except RuntimeError as e:
440
print(f"Platform limitation: {e}")
441
```