dln2 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dln2-0.2.0 → dln2-0.2.2}/PKG-INFO +60 -24
- {dln2-0.2.0 → dln2-0.2.2}/README.md +58 -11
- {dln2-0.2.0 → dln2-0.2.2}/dln2/__init__.py +0 -1
- {dln2-0.2.0 → dln2-0.2.2}/dln2/_core.py +14 -6
- {dln2-0.2.0 → dln2-0.2.2}/dln2/cli.py +10 -44
- {dln2-0.2.0 → dln2-0.2.2}/dln2/i2c.py +13 -9
- {dln2-0.2.0 → dln2-0.2.2}/dln2/spi.py +22 -3
- {dln2-0.2.0 → dln2-0.2.2}/dln2.egg-info/PKG-INFO +60 -24
- {dln2-0.2.0 → dln2-0.2.2}/dln2.egg-info/entry_points.txt +0 -1
- {dln2-0.2.0 → dln2-0.2.2}/setup.py +2 -3
- {dln2-0.2.0 → dln2-0.2.2}/LICENSE +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/dln2/adc.py +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/dln2/bme280.py +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/dln2/gpio.py +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/dln2/py.typed +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/dln2.egg-info/SOURCES.txt +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/dln2.egg-info/dependency_links.txt +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/dln2.egg-info/requires.txt +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/dln2.egg-info/top_level.txt +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/pyproject.toml +0 -0
- {dln2-0.2.0 → dln2-0.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: dln2
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Unified DLN2 driver — SPI, GPIO, I2C, ADC over a single USB connection. Originally developed for the Pico USB I/O Board.
|
|
5
5
|
Home-page: https://github.com/syabyr/dln2_wrapper
|
|
6
6
|
Author: IPM Group
|
|
@@ -21,17 +21,6 @@ Requires-Python: >=3.9
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: pyusb>=1.2
|
|
24
|
-
Dynamic: author
|
|
25
|
-
Dynamic: author-email
|
|
26
|
-
Dynamic: classifier
|
|
27
|
-
Dynamic: description
|
|
28
|
-
Dynamic: description-content-type
|
|
29
|
-
Dynamic: home-page
|
|
30
|
-
Dynamic: license
|
|
31
|
-
Dynamic: license-file
|
|
32
|
-
Dynamic: requires-dist
|
|
33
|
-
Dynamic: requires-python
|
|
34
|
-
Dynamic: summary
|
|
35
24
|
|
|
36
25
|
# dln2 — Unified DLN2 USB I/O Board Wrapper
|
|
37
26
|
|
|
@@ -118,10 +107,6 @@ smbus-compatible API: `write_byte()`, `read_byte_data()`,
|
|
|
118
107
|
`read_channel(channel)`, `read_all()`, `read_volts(channel)`,
|
|
119
108
|
`set_resolution(bits)`, `enable_channel(ch)`, `disable_channel(ch)`
|
|
120
109
|
|
|
121
|
-
### BME280
|
|
122
|
-
`read_chip_id()`, `get_temperature()`, `get_humidity()`, `get_pressure()`,
|
|
123
|
-
`get_altitude()`, `get_all()`
|
|
124
|
-
|
|
125
110
|
## CLI Commands
|
|
126
111
|
|
|
127
112
|
After installation, the following commands are available:
|
|
@@ -135,7 +120,6 @@ After installation, the following commands are available:
|
|
|
135
120
|
| `dln2-adc-watch` | Stream one ADC channel |
|
|
136
121
|
| `dln2-i2c-scan` | Scan I2C bus for devices |
|
|
137
122
|
| `dln2-i2c-test` | Read/write I2C register |
|
|
138
|
-
| `dln2-bme280` | Read BME280 sensor measurements |
|
|
139
123
|
| `dln2-spi-test` | Send SPI JEDEC ID probe |
|
|
140
124
|
| `dln2-bpw-test` | Cycle SPI bits-per-word 4..16 |
|
|
141
125
|
|
|
@@ -153,28 +137,59 @@ dln2-adc-watch --channel 0 --interval 0.5
|
|
|
153
137
|
dln2-i2c-scan
|
|
154
138
|
dln2-i2c-test --address 0x76 --register 0xD0 --read 1
|
|
155
139
|
|
|
156
|
-
# BME280
|
|
157
|
-
dln2-bme280 --address 0x76 --pretty
|
|
158
|
-
|
|
159
140
|
# SPI
|
|
160
141
|
dln2-spi-test
|
|
161
142
|
dln2-bpw-test
|
|
162
143
|
```
|
|
163
144
|
|
|
164
|
-
## Examples
|
|
145
|
+
## Examples
|
|
146
|
+
|
|
147
|
+
### Core examples (this repo)
|
|
148
|
+
|
|
149
|
+
Basic I/O operations that exercise the wrapper itself:
|
|
165
150
|
|
|
166
151
|
```bash
|
|
167
152
|
# SPI JEDEC ID read
|
|
168
153
|
python3 examples/spidev_test.py
|
|
169
154
|
|
|
155
|
+
# SPI bits-per-word cycle
|
|
156
|
+
python3 examples/bpw_tester.py
|
|
157
|
+
|
|
170
158
|
# GPIO pin toggle (default: Pico LED)
|
|
171
159
|
python3 examples/gpio_toggle.py --pin 25
|
|
172
160
|
|
|
161
|
+
# GPIO pin info
|
|
162
|
+
python3 examples/gpio_info.py --pin 2
|
|
163
|
+
|
|
164
|
+
# GPIO event watch
|
|
165
|
+
python3 examples/gpio_watch.py --pin 2
|
|
166
|
+
|
|
173
167
|
# I2C bus scan
|
|
174
168
|
python3 examples/i2c_scan.py
|
|
175
169
|
|
|
176
|
-
#
|
|
170
|
+
# I2C register read/write
|
|
171
|
+
python3 examples/i2c_test.py --address 0x76 --register 0xD0 --read 1
|
|
172
|
+
|
|
173
|
+
# ADC read
|
|
177
174
|
python3 examples/adc_info.py
|
|
175
|
+
|
|
176
|
+
# ADC stream
|
|
177
|
+
python3 examples/adc_watch.py --channel 0
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Device-specific examples (separate repo)
|
|
181
|
+
|
|
182
|
+
Sensor test scripts live in [dln2_examples](https://github.com/syabyr/dln2_examples):
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git clone https://github.com/syabyr/dln2_examples
|
|
186
|
+
cd dln2_examples/i2c
|
|
187
|
+
|
|
188
|
+
python3 bh1750_full_test.py # BH1750 ambient light @ 0x23
|
|
189
|
+
python3 bme280_full_test.py # BME280 temp/press/humid @ 0x76
|
|
190
|
+
python3 bmp180_full_test.py # BMP180 temp/pressure @ 0x77
|
|
191
|
+
python3 tsl2561_full_test.py # TSL2561 ambient light @ 0x39
|
|
192
|
+
python3 mpu9250_full_test.py # MPU9250 9-axis IMU @ 0x68
|
|
178
193
|
```
|
|
179
194
|
|
|
180
195
|
## Why Unified?
|
|
@@ -187,10 +202,31 @@ python3 examples/adc_info.py
|
|
|
187
202
|
| Multi-device | Manual USB enumeration | `list_devices()` |
|
|
188
203
|
| Cross-module data flow | Impossible (4 echo counters) | Trivial (same counter) |
|
|
189
204
|
|
|
205
|
+
## Pin Constraints
|
|
206
|
+
|
|
207
|
+
| Pins | Capability |
|
|
208
|
+
|---|---|
|
|
209
|
+
| 0–22, 26–28 | Full GPIO (input/output + events) |
|
|
210
|
+
| 23, 24, 29 | Output-only (reserved for SMPS / VBUS / VSYS) |
|
|
211
|
+
| 25 | Output-only (on-board LED) |
|
|
212
|
+
| ADC 0–2 | Map to GPIO 26, 27, 28 respectively |
|
|
213
|
+
|
|
214
|
+
## Firmware Compatibility
|
|
215
|
+
|
|
216
|
+
| dln2 Python | Minimum firmware | Notes |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| 0.2.1 | [pico-usb-io-board](https://github.com/syabyr/pico-usb-io-board) `>= v0.2` | SPI bpw validation, ADC pin-sharing fix |
|
|
219
|
+
| 0.2.0 | any | Works with all firmware versions |
|
|
220
|
+
|
|
221
|
+
To flash updated firmware:
|
|
222
|
+
1. Hold the BOOTSEL button while connecting the Pico (or press and release RESET while holding BOOTSEL)
|
|
223
|
+
2. Copy `dln2.uf2` to the `RPI-RP2` USB drive that appears
|
|
224
|
+
3. The Pico reboots automatically with the new firmware
|
|
225
|
+
|
|
190
226
|
## Related
|
|
191
227
|
|
|
192
228
|
- [pico-usb-io-board](https://github.com/syabyr/pico-usb-io-board) — RP2040 firmware
|
|
193
|
-
- [
|
|
229
|
+
- [dln2_examples](https://github.com/syabyr/dln2_examples) — sensor test scripts (BH1750, BME280, BMP180, TSL2561, MPU9250, …)
|
|
194
230
|
|
|
195
231
|
## License
|
|
196
232
|
|
|
@@ -83,10 +83,6 @@ smbus-compatible API: `write_byte()`, `read_byte_data()`,
|
|
|
83
83
|
`read_channel(channel)`, `read_all()`, `read_volts(channel)`,
|
|
84
84
|
`set_resolution(bits)`, `enable_channel(ch)`, `disable_channel(ch)`
|
|
85
85
|
|
|
86
|
-
### BME280
|
|
87
|
-
`read_chip_id()`, `get_temperature()`, `get_humidity()`, `get_pressure()`,
|
|
88
|
-
`get_altitude()`, `get_all()`
|
|
89
|
-
|
|
90
86
|
## CLI Commands
|
|
91
87
|
|
|
92
88
|
After installation, the following commands are available:
|
|
@@ -100,7 +96,6 @@ After installation, the following commands are available:
|
|
|
100
96
|
| `dln2-adc-watch` | Stream one ADC channel |
|
|
101
97
|
| `dln2-i2c-scan` | Scan I2C bus for devices |
|
|
102
98
|
| `dln2-i2c-test` | Read/write I2C register |
|
|
103
|
-
| `dln2-bme280` | Read BME280 sensor measurements |
|
|
104
99
|
| `dln2-spi-test` | Send SPI JEDEC ID probe |
|
|
105
100
|
| `dln2-bpw-test` | Cycle SPI bits-per-word 4..16 |
|
|
106
101
|
|
|
@@ -118,28 +113,59 @@ dln2-adc-watch --channel 0 --interval 0.5
|
|
|
118
113
|
dln2-i2c-scan
|
|
119
114
|
dln2-i2c-test --address 0x76 --register 0xD0 --read 1
|
|
120
115
|
|
|
121
|
-
# BME280
|
|
122
|
-
dln2-bme280 --address 0x76 --pretty
|
|
123
|
-
|
|
124
116
|
# SPI
|
|
125
117
|
dln2-spi-test
|
|
126
118
|
dln2-bpw-test
|
|
127
119
|
```
|
|
128
120
|
|
|
129
|
-
## Examples
|
|
121
|
+
## Examples
|
|
122
|
+
|
|
123
|
+
### Core examples (this repo)
|
|
124
|
+
|
|
125
|
+
Basic I/O operations that exercise the wrapper itself:
|
|
130
126
|
|
|
131
127
|
```bash
|
|
132
128
|
# SPI JEDEC ID read
|
|
133
129
|
python3 examples/spidev_test.py
|
|
134
130
|
|
|
131
|
+
# SPI bits-per-word cycle
|
|
132
|
+
python3 examples/bpw_tester.py
|
|
133
|
+
|
|
135
134
|
# GPIO pin toggle (default: Pico LED)
|
|
136
135
|
python3 examples/gpio_toggle.py --pin 25
|
|
137
136
|
|
|
137
|
+
# GPIO pin info
|
|
138
|
+
python3 examples/gpio_info.py --pin 2
|
|
139
|
+
|
|
140
|
+
# GPIO event watch
|
|
141
|
+
python3 examples/gpio_watch.py --pin 2
|
|
142
|
+
|
|
138
143
|
# I2C bus scan
|
|
139
144
|
python3 examples/i2c_scan.py
|
|
140
145
|
|
|
141
|
-
#
|
|
146
|
+
# I2C register read/write
|
|
147
|
+
python3 examples/i2c_test.py --address 0x76 --register 0xD0 --read 1
|
|
148
|
+
|
|
149
|
+
# ADC read
|
|
142
150
|
python3 examples/adc_info.py
|
|
151
|
+
|
|
152
|
+
# ADC stream
|
|
153
|
+
python3 examples/adc_watch.py --channel 0
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Device-specific examples (separate repo)
|
|
157
|
+
|
|
158
|
+
Sensor test scripts live in [dln2_examples](https://github.com/syabyr/dln2_examples):
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
git clone https://github.com/syabyr/dln2_examples
|
|
162
|
+
cd dln2_examples/i2c
|
|
163
|
+
|
|
164
|
+
python3 bh1750_full_test.py # BH1750 ambient light @ 0x23
|
|
165
|
+
python3 bme280_full_test.py # BME280 temp/press/humid @ 0x76
|
|
166
|
+
python3 bmp180_full_test.py # BMP180 temp/pressure @ 0x77
|
|
167
|
+
python3 tsl2561_full_test.py # TSL2561 ambient light @ 0x39
|
|
168
|
+
python3 mpu9250_full_test.py # MPU9250 9-axis IMU @ 0x68
|
|
143
169
|
```
|
|
144
170
|
|
|
145
171
|
## Why Unified?
|
|
@@ -152,10 +178,31 @@ python3 examples/adc_info.py
|
|
|
152
178
|
| Multi-device | Manual USB enumeration | `list_devices()` |
|
|
153
179
|
| Cross-module data flow | Impossible (4 echo counters) | Trivial (same counter) |
|
|
154
180
|
|
|
181
|
+
## Pin Constraints
|
|
182
|
+
|
|
183
|
+
| Pins | Capability |
|
|
184
|
+
|---|---|
|
|
185
|
+
| 0–22, 26–28 | Full GPIO (input/output + events) |
|
|
186
|
+
| 23, 24, 29 | Output-only (reserved for SMPS / VBUS / VSYS) |
|
|
187
|
+
| 25 | Output-only (on-board LED) |
|
|
188
|
+
| ADC 0–2 | Map to GPIO 26, 27, 28 respectively |
|
|
189
|
+
|
|
190
|
+
## Firmware Compatibility
|
|
191
|
+
|
|
192
|
+
| dln2 Python | Minimum firmware | Notes |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| 0.2.1 | [pico-usb-io-board](https://github.com/syabyr/pico-usb-io-board) `>= v0.2` | SPI bpw validation, ADC pin-sharing fix |
|
|
195
|
+
| 0.2.0 | any | Works with all firmware versions |
|
|
196
|
+
|
|
197
|
+
To flash updated firmware:
|
|
198
|
+
1. Hold the BOOTSEL button while connecting the Pico (or press and release RESET while holding BOOTSEL)
|
|
199
|
+
2. Copy `dln2.uf2` to the `RPI-RP2` USB drive that appears
|
|
200
|
+
3. The Pico reboots automatically with the new firmware
|
|
201
|
+
|
|
155
202
|
## Related
|
|
156
203
|
|
|
157
204
|
- [pico-usb-io-board](https://github.com/syabyr/pico-usb-io-board) — RP2040 firmware
|
|
158
|
-
- [
|
|
205
|
+
- [dln2_examples](https://github.com/syabyr/dln2_examples) — sensor test scripts (BH1750, BME280, BMP180, TSL2561, MPU9250, …)
|
|
159
206
|
|
|
160
207
|
## License
|
|
161
208
|
|
|
@@ -127,18 +127,25 @@ class Dln2Connection:
|
|
|
127
127
|
self._setup()
|
|
128
128
|
|
|
129
129
|
def _setup(self):
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
130
|
+
# NOTE: Do NOT call self._dev.reset() here.
|
|
131
|
+
# A USB bus reset only resets the USB peripheral on the RP2040 —
|
|
132
|
+
# SPI PIO/DMA state machines survive it. Commands that hit a
|
|
133
|
+
# half-configured PIO cause a hard fault that kills the firmware
|
|
134
|
+
# and requires a physical replug. Instead, just set configuration.
|
|
135
135
|
|
|
136
136
|
try:
|
|
137
137
|
self._dev.set_configuration()
|
|
138
138
|
except Exception:
|
|
139
139
|
pass
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
try:
|
|
142
|
+
cfg = self._dev.get_active_configuration()
|
|
143
|
+
except Exception:
|
|
144
|
+
raise RuntimeError(
|
|
145
|
+
"DLN2 device is not responding — "
|
|
146
|
+
"the Pico firmware may have crashed.\n"
|
|
147
|
+
"Please replug the USB cable to power-cycle the board."
|
|
148
|
+
)
|
|
142
149
|
intf = cfg[(0, 0)]
|
|
143
150
|
|
|
144
151
|
ep_out = ep_in = None
|
|
@@ -409,6 +416,7 @@ class Dln2Connection:
|
|
|
409
416
|
return resp
|
|
410
417
|
|
|
411
418
|
def i2c_read(self, addr, length, mem_addr_len=0, mem_addr=0):
|
|
419
|
+
# Use 9-byte header — firmware expects BBBIH for reads
|
|
412
420
|
payload = struct.pack("<BBBIH", 0, int(addr) & 0x7F,
|
|
413
421
|
int(mem_addr_len) & 0xFF,
|
|
414
422
|
int(mem_addr) & 0xFFFFFFFF,
|
|
@@ -282,40 +282,6 @@ def i2c_test():
|
|
|
282
282
|
sys.exit(1)
|
|
283
283
|
|
|
284
284
|
|
|
285
|
-
# ═══════════════════════════════════════════════════════════════
|
|
286
|
-
# BME280
|
|
287
|
-
# ═══════════════════════════════════════════════════════════════
|
|
288
|
-
|
|
289
|
-
def bme280():
|
|
290
|
-
"""Read BME280 sensor values (chip ID + measurements)."""
|
|
291
|
-
from dln2.bme280 import BME280
|
|
292
|
-
|
|
293
|
-
parser = argparse.ArgumentParser(description="Read BME280 values over DLN2 I2C")
|
|
294
|
-
parser.add_argument("--address", type=_parse_int, default=0x76, help="BME280 I2C address")
|
|
295
|
-
parser.add_argument("--pretty", action="store_true", help="Print pretty JSON")
|
|
296
|
-
args = parser.parse_args()
|
|
297
|
-
|
|
298
|
-
try:
|
|
299
|
-
with BME280(address=args.address) as sensor:
|
|
300
|
-
chip_id = sensor.check_chip_id()
|
|
301
|
-
sensor.read_calibration()
|
|
302
|
-
sensor.configure()
|
|
303
|
-
measurement = sensor.read_measurements()
|
|
304
|
-
|
|
305
|
-
payload = {
|
|
306
|
-
"address": args.address,
|
|
307
|
-
"chip_id": chip_id,
|
|
308
|
-
**measurement,
|
|
309
|
-
}
|
|
310
|
-
if args.pretty:
|
|
311
|
-
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
312
|
-
else:
|
|
313
|
-
print(json.dumps(payload, sort_keys=True))
|
|
314
|
-
except RuntimeError as e:
|
|
315
|
-
print(f"Error: {e}", file=sys.stderr)
|
|
316
|
-
sys.exit(1)
|
|
317
|
-
|
|
318
|
-
|
|
319
285
|
# ═══════════════════════════════════════════════════════════════
|
|
320
286
|
# SPI
|
|
321
287
|
# ═══════════════════════════════════════════════════════════════
|
|
@@ -339,10 +305,10 @@ def spi_test():
|
|
|
339
305
|
|
|
340
306
|
try:
|
|
341
307
|
print("Opening SpiDev (DLN backend)...")
|
|
342
|
-
dev.open(0, 0)
|
|
343
308
|
dev.max_speed_hz = 1_000_000
|
|
344
309
|
dev.mode = 0
|
|
345
310
|
dev.bits_per_word = 8
|
|
311
|
+
dev.open(0, 0)
|
|
346
312
|
tx = [0x9F, 0x00, 0x00, 0x00]
|
|
347
313
|
print("Sending JEDEC (0x9F) via DLN wrapper... host_hold_cs=", dev.host_hold_cs)
|
|
348
314
|
rx = dev.xfer2(tx)
|
|
@@ -359,23 +325,22 @@ def spi_test():
|
|
|
359
325
|
|
|
360
326
|
|
|
361
327
|
def bpw_test():
|
|
362
|
-
"""Cycle SPI bits-per-word
|
|
328
|
+
"""Cycle SPI bits-per-word 8 and 16 sending a test pattern at each setting."""
|
|
363
329
|
from dln2.spi import SpiDev
|
|
364
330
|
|
|
365
|
-
print("Sending one transfer for
|
|
331
|
+
print("Sending one transfer for BPW 8 and 16")
|
|
366
332
|
try:
|
|
367
333
|
with SpiDev() as dev:
|
|
368
|
-
dev.open(0, 0)
|
|
369
334
|
dev.max_speed_hz = 1_000_000
|
|
370
335
|
dev.mode = 0
|
|
371
336
|
|
|
372
|
-
for bpw in
|
|
337
|
+
for bpw in (8, 16):
|
|
373
338
|
dev.bits_per_word = bpw
|
|
339
|
+
dev.open(0, 0)
|
|
374
340
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
pattern = 0xAAAA & ((1 << bpw) - 1)
|
|
341
|
+
mask = (1 << bpw) - 1
|
|
342
|
+
pattern = 0xAA if bpw <= 8 else 0xAAAA
|
|
343
|
+
pattern &= mask
|
|
379
344
|
|
|
380
345
|
try:
|
|
381
346
|
rx = dev.xfer2([pattern])
|
|
@@ -383,7 +348,8 @@ def bpw_test():
|
|
|
383
348
|
except Exception as e:
|
|
384
349
|
print(f"bpw={bpw:2d} tx={pattern:#06x} FAILED: {e}")
|
|
385
350
|
|
|
386
|
-
|
|
351
|
+
dev.close()
|
|
352
|
+
time.sleep(0.2)
|
|
387
353
|
|
|
388
354
|
print("Done.")
|
|
389
355
|
except RuntimeError as e:
|
|
@@ -103,24 +103,28 @@ class SMBus:
|
|
|
103
103
|
return self._require().i2c_read(addr, 1)[0]
|
|
104
104
|
|
|
105
105
|
def read_byte_data(self, addr, cmd):
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
conn = self._require()
|
|
107
|
+
conn.i2c_write(addr, [cmd])
|
|
108
|
+
return conn.i2c_read(addr, 1)[0]
|
|
108
109
|
|
|
109
110
|
def read_word_data(self, addr, cmd):
|
|
110
|
-
|
|
111
|
+
conn = self._require()
|
|
112
|
+
conn.i2c_write(addr, [cmd])
|
|
113
|
+
rx = conn.i2c_read(addr, 2)
|
|
111
114
|
return rx[0] | (rx[1] << 8)
|
|
112
115
|
|
|
113
116
|
def read_block_data(self, addr, cmd):
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
+
conn = self._require()
|
|
118
|
+
conn.i2c_write(addr, [cmd])
|
|
119
|
+
count = conn.i2c_read(addr, 1)[0]
|
|
117
120
|
if count == 0:
|
|
118
121
|
return []
|
|
119
|
-
return list(
|
|
122
|
+
return list(conn.i2c_read(addr, count))
|
|
120
123
|
|
|
121
124
|
def read_i2c_block_data(self, addr, cmd, length=32):
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
conn = self._require()
|
|
126
|
+
conn.i2c_write(addr, [cmd])
|
|
127
|
+
return list(conn.i2c_read(addr, length))
|
|
124
128
|
|
|
125
129
|
# ── Raw access ─────────────────────────────────────
|
|
126
130
|
|
|
@@ -46,14 +46,33 @@ class SpiDev:
|
|
|
46
46
|
|
|
47
47
|
@bits_per_word.setter
|
|
48
48
|
def bits_per_word(self, v):
|
|
49
|
-
|
|
49
|
+
v = int(v)
|
|
50
|
+
# RP2040 PIO SPI only reliably supports 8 and 16 bit frames.
|
|
51
|
+
# Other values (4-7, 9-15) can trigger firmware DMA/PIO bugs.
|
|
52
|
+
if v not in (8, 16):
|
|
53
|
+
raise ValueError(
|
|
54
|
+
f"bits_per_word must be 8 or 16, got {v}. "
|
|
55
|
+
f"The Pico firmware does not support other frame sizes."
|
|
56
|
+
)
|
|
57
|
+
self._bits_per_word = v
|
|
50
58
|
|
|
51
59
|
def open(self, bus=0, device=0):
|
|
52
|
-
"""
|
|
60
|
+
"""Enable the SPI module. For spidev compatibility."""
|
|
61
|
+
self._conn.spi_enable()
|
|
62
|
+
self._configure()
|
|
53
63
|
return self
|
|
54
64
|
|
|
55
65
|
def close(self):
|
|
56
|
-
|
|
66
|
+
"""Disable the SPI module so the firmware releases its PIO/DMA state."""
|
|
67
|
+
try:
|
|
68
|
+
self._conn.spi_disable()
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def _configure(self):
|
|
73
|
+
"""Sync current settings to the hardware."""
|
|
74
|
+
self._conn.spi_configure(self._mode, self._max_speed_hz,
|
|
75
|
+
self._bits_per_word)
|
|
57
76
|
|
|
58
77
|
def xfer2(self, data):
|
|
59
78
|
"""Full-duplex transfer. Returns MISO data."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: dln2
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Unified DLN2 driver — SPI, GPIO, I2C, ADC over a single USB connection. Originally developed for the Pico USB I/O Board.
|
|
5
5
|
Home-page: https://github.com/syabyr/dln2_wrapper
|
|
6
6
|
Author: IPM Group
|
|
@@ -21,17 +21,6 @@ Requires-Python: >=3.9
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Requires-Dist: pyusb>=1.2
|
|
24
|
-
Dynamic: author
|
|
25
|
-
Dynamic: author-email
|
|
26
|
-
Dynamic: classifier
|
|
27
|
-
Dynamic: description
|
|
28
|
-
Dynamic: description-content-type
|
|
29
|
-
Dynamic: home-page
|
|
30
|
-
Dynamic: license
|
|
31
|
-
Dynamic: license-file
|
|
32
|
-
Dynamic: requires-dist
|
|
33
|
-
Dynamic: requires-python
|
|
34
|
-
Dynamic: summary
|
|
35
24
|
|
|
36
25
|
# dln2 — Unified DLN2 USB I/O Board Wrapper
|
|
37
26
|
|
|
@@ -118,10 +107,6 @@ smbus-compatible API: `write_byte()`, `read_byte_data()`,
|
|
|
118
107
|
`read_channel(channel)`, `read_all()`, `read_volts(channel)`,
|
|
119
108
|
`set_resolution(bits)`, `enable_channel(ch)`, `disable_channel(ch)`
|
|
120
109
|
|
|
121
|
-
### BME280
|
|
122
|
-
`read_chip_id()`, `get_temperature()`, `get_humidity()`, `get_pressure()`,
|
|
123
|
-
`get_altitude()`, `get_all()`
|
|
124
|
-
|
|
125
110
|
## CLI Commands
|
|
126
111
|
|
|
127
112
|
After installation, the following commands are available:
|
|
@@ -135,7 +120,6 @@ After installation, the following commands are available:
|
|
|
135
120
|
| `dln2-adc-watch` | Stream one ADC channel |
|
|
136
121
|
| `dln2-i2c-scan` | Scan I2C bus for devices |
|
|
137
122
|
| `dln2-i2c-test` | Read/write I2C register |
|
|
138
|
-
| `dln2-bme280` | Read BME280 sensor measurements |
|
|
139
123
|
| `dln2-spi-test` | Send SPI JEDEC ID probe |
|
|
140
124
|
| `dln2-bpw-test` | Cycle SPI bits-per-word 4..16 |
|
|
141
125
|
|
|
@@ -153,28 +137,59 @@ dln2-adc-watch --channel 0 --interval 0.5
|
|
|
153
137
|
dln2-i2c-scan
|
|
154
138
|
dln2-i2c-test --address 0x76 --register 0xD0 --read 1
|
|
155
139
|
|
|
156
|
-
# BME280
|
|
157
|
-
dln2-bme280 --address 0x76 --pretty
|
|
158
|
-
|
|
159
140
|
# SPI
|
|
160
141
|
dln2-spi-test
|
|
161
142
|
dln2-bpw-test
|
|
162
143
|
```
|
|
163
144
|
|
|
164
|
-
## Examples
|
|
145
|
+
## Examples
|
|
146
|
+
|
|
147
|
+
### Core examples (this repo)
|
|
148
|
+
|
|
149
|
+
Basic I/O operations that exercise the wrapper itself:
|
|
165
150
|
|
|
166
151
|
```bash
|
|
167
152
|
# SPI JEDEC ID read
|
|
168
153
|
python3 examples/spidev_test.py
|
|
169
154
|
|
|
155
|
+
# SPI bits-per-word cycle
|
|
156
|
+
python3 examples/bpw_tester.py
|
|
157
|
+
|
|
170
158
|
# GPIO pin toggle (default: Pico LED)
|
|
171
159
|
python3 examples/gpio_toggle.py --pin 25
|
|
172
160
|
|
|
161
|
+
# GPIO pin info
|
|
162
|
+
python3 examples/gpio_info.py --pin 2
|
|
163
|
+
|
|
164
|
+
# GPIO event watch
|
|
165
|
+
python3 examples/gpio_watch.py --pin 2
|
|
166
|
+
|
|
173
167
|
# I2C bus scan
|
|
174
168
|
python3 examples/i2c_scan.py
|
|
175
169
|
|
|
176
|
-
#
|
|
170
|
+
# I2C register read/write
|
|
171
|
+
python3 examples/i2c_test.py --address 0x76 --register 0xD0 --read 1
|
|
172
|
+
|
|
173
|
+
# ADC read
|
|
177
174
|
python3 examples/adc_info.py
|
|
175
|
+
|
|
176
|
+
# ADC stream
|
|
177
|
+
python3 examples/adc_watch.py --channel 0
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Device-specific examples (separate repo)
|
|
181
|
+
|
|
182
|
+
Sensor test scripts live in [dln2_examples](https://github.com/syabyr/dln2_examples):
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git clone https://github.com/syabyr/dln2_examples
|
|
186
|
+
cd dln2_examples/i2c
|
|
187
|
+
|
|
188
|
+
python3 bh1750_full_test.py # BH1750 ambient light @ 0x23
|
|
189
|
+
python3 bme280_full_test.py # BME280 temp/press/humid @ 0x76
|
|
190
|
+
python3 bmp180_full_test.py # BMP180 temp/pressure @ 0x77
|
|
191
|
+
python3 tsl2561_full_test.py # TSL2561 ambient light @ 0x39
|
|
192
|
+
python3 mpu9250_full_test.py # MPU9250 9-axis IMU @ 0x68
|
|
178
193
|
```
|
|
179
194
|
|
|
180
195
|
## Why Unified?
|
|
@@ -187,10 +202,31 @@ python3 examples/adc_info.py
|
|
|
187
202
|
| Multi-device | Manual USB enumeration | `list_devices()` |
|
|
188
203
|
| Cross-module data flow | Impossible (4 echo counters) | Trivial (same counter) |
|
|
189
204
|
|
|
205
|
+
## Pin Constraints
|
|
206
|
+
|
|
207
|
+
| Pins | Capability |
|
|
208
|
+
|---|---|
|
|
209
|
+
| 0–22, 26–28 | Full GPIO (input/output + events) |
|
|
210
|
+
| 23, 24, 29 | Output-only (reserved for SMPS / VBUS / VSYS) |
|
|
211
|
+
| 25 | Output-only (on-board LED) |
|
|
212
|
+
| ADC 0–2 | Map to GPIO 26, 27, 28 respectively |
|
|
213
|
+
|
|
214
|
+
## Firmware Compatibility
|
|
215
|
+
|
|
216
|
+
| dln2 Python | Minimum firmware | Notes |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| 0.2.1 | [pico-usb-io-board](https://github.com/syabyr/pico-usb-io-board) `>= v0.2` | SPI bpw validation, ADC pin-sharing fix |
|
|
219
|
+
| 0.2.0 | any | Works with all firmware versions |
|
|
220
|
+
|
|
221
|
+
To flash updated firmware:
|
|
222
|
+
1. Hold the BOOTSEL button while connecting the Pico (or press and release RESET while holding BOOTSEL)
|
|
223
|
+
2. Copy `dln2.uf2` to the `RPI-RP2` USB drive that appears
|
|
224
|
+
3. The Pico reboots automatically with the new firmware
|
|
225
|
+
|
|
190
226
|
## Related
|
|
191
227
|
|
|
192
228
|
- [pico-usb-io-board](https://github.com/syabyr/pico-usb-io-board) — RP2040 firmware
|
|
193
|
-
- [
|
|
229
|
+
- [dln2_examples](https://github.com/syabyr/dln2_examples) — sensor test scripts (BH1750, BME280, BMP180, TSL2561, MPU9250, …)
|
|
194
230
|
|
|
195
231
|
## License
|
|
196
232
|
|
|
@@ -5,7 +5,7 @@ from setuptools import setup, find_packages
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="dln2",
|
|
8
|
-
version="0.2.
|
|
8
|
+
version="0.2.2",
|
|
9
9
|
description="Unified DLN2 driver — SPI, GPIO, I2C, ADC over a single USB connection. "
|
|
10
10
|
"Originally developed for the Pico USB I/O Board.",
|
|
11
11
|
long_description=open("README.md").read(),
|
|
@@ -14,7 +14,7 @@ setup(
|
|
|
14
14
|
author="IPM Group",
|
|
15
15
|
author_email="contact@ipmgroup.dev",
|
|
16
16
|
license="MIT",
|
|
17
|
-
packages=find_packages(exclude=["examples"]),
|
|
17
|
+
packages=find_packages(exclude=["examples", "i2c", "i2c.*"]),
|
|
18
18
|
package_data={"dln2": ["py.typed"]},
|
|
19
19
|
install_requires=[
|
|
20
20
|
"pyusb>=1.2",
|
|
@@ -29,7 +29,6 @@ setup(
|
|
|
29
29
|
"dln2-adc-watch=dln2.cli:adc_watch",
|
|
30
30
|
"dln2-i2c-scan=dln2.cli:i2c_scan",
|
|
31
31
|
"dln2-i2c-test=dln2.cli:i2c_test",
|
|
32
|
-
"dln2-bme280=dln2.cli:bme280",
|
|
33
32
|
"dln2-spi-test=dln2.cli:spi_test",
|
|
34
33
|
"dln2-bpw-test=dln2.cli:bpw_test",
|
|
35
34
|
],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|