circuitpython-adxl36x 0.1.0__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.
- circuitpython_adxl36x-0.1.0/LICENSE +21 -0
- circuitpython_adxl36x-0.1.0/PKG-INFO +75 -0
- circuitpython_adxl36x-0.1.0/README.md +52 -0
- circuitpython_adxl36x-0.1.0/pyproject.toml +117 -0
- circuitpython_adxl36x-0.1.0/pyproject.toml.orig +108 -0
- circuitpython_adxl36x-0.1.0/src/adxl36x/__init__.py +747 -0
- circuitpython_adxl36x-0.1.0/src/adxl36x/py.typed +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mike Mabey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: circuitpython-adxl36x
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CircuitPython driver for the Analog Devices ADXL366/ADXL367 accelerometers
|
|
5
|
+
Keywords: adxl366,adxl367,accelerometer,blinka,circuitpython,hardware,i2c,micropython,spi
|
|
6
|
+
Author: Mike Mabey
|
|
7
|
+
Author-email: Mike Mabey <email@mikemabey.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Topic :: System :: Hardware
|
|
16
|
+
Requires-Dist: adafruit-circuitpython-busdevice>=5.2.11
|
|
17
|
+
Requires-Dist: circuitpython-stubs>=10.2.1
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Project-URL: Homepage, https://github.com/mmabey/circuitpython-adxl36x
|
|
20
|
+
Project-URL: Repository, https://github.com/mmabey/circuitpython-adxl36x
|
|
21
|
+
Project-URL: Issues, https://github.com/mmabey/circuitpython-adxl36x/issues
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# circuitpython-adxl36x
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/circuitpython-adxl36x/)
|
|
27
|
+
[](https://pypi.org/project/circuitpython-adxl36x/)
|
|
28
|
+
[](LICENSE)
|
|
29
|
+
[](https://github.com/mmabey/circuitpython-adxl36x/actions/workflows/ci.yml)
|
|
30
|
+
[](https://pypi.org/project/circuitpython-adxl36x/)
|
|
31
|
+
[](https://github.com/astral-sh/ruff)
|
|
32
|
+
|
|
33
|
+
CircuitPython driver for the Analog Devices [ADXL366](https://www.analog.com/en/products/adxl366.html) and
|
|
34
|
+
[ADXL367](https://www.analog.com/en/products/adxl367.html) ultra-low-power 3-axis MEMS accelerometers.
|
|
35
|
+
|
|
36
|
+
`ADXL367` implements the full shared register map and is the base class. `ADXL366` subclasses it, adding the pedometer,
|
|
37
|
+
Z-axis nonlinearity compensation, and activity/inactivity reference readback - the three features the ADXL367 silicon
|
|
38
|
+
doesn't implement. Both support SPI and I2C.
|
|
39
|
+
|
|
40
|
+
## Relationship to `adafruit_adxl34x`
|
|
41
|
+
|
|
42
|
+
This library's public API deliberately mirrors the shape of [Adafruit's
|
|
43
|
+
`adafruit_adxl34x`](https://github.com/adafruit/Adafruit_CircuitPython_ADXL34x) driver for the ADXL34x family
|
|
44
|
+
(`.acceleration`, `.events`, `enable_tap_detection()`, `enable_motion_detection()`, `data_rate`) so that code written
|
|
45
|
+
against a 34x-family chip needs minimal changes to run against a 366/367. The two chip families are **not**
|
|
46
|
+
register-compatible — only the driver's Python API shape is shared. Notable differences:
|
|
47
|
+
|
|
48
|
+
- `range` is named `g_range` here instead — `range` shadows the Python builtin, and compatibility with the ADXL34x API
|
|
49
|
+
takes a back seat to that on this one property.
|
|
50
|
+
- No `enable_freefall_detection()` — the 366/367 has no freefall-detection hardware.
|
|
51
|
+
- `Range` tops out at `RANGE_8_G` — the 366/367 doesn't support ±16g.
|
|
52
|
+
- `DataRate` only has 6 discrete rates (12.5 Hz-400 Hz) vs. the 34x's 16.
|
|
53
|
+
- `.events` includes many more keys (FIFO, pedometer, ADC/temperature thresholds) since the 366/367 exposes far more
|
|
54
|
+
interrupt sources.
|
|
55
|
+
|
|
56
|
+
## Installation
|
|
57
|
+
|
|
58
|
+
On a CircuitPython board, copy `src/adxl36x/` into `CIRCUITPY/lib/adxl36x/`, along with the `adafruit_bus_device` bundle
|
|
59
|
+
library.
|
|
60
|
+
|
|
61
|
+
For host-side development (type checking, tests) in another project:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv add --editable ~/src/circuitpython-adxl36x
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Development
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv sync
|
|
71
|
+
uv run ruff check --fix
|
|
72
|
+
uv run ruff format
|
|
73
|
+
uv run ty check
|
|
74
|
+
uv run pytest
|
|
75
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# circuitpython-adxl36x
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/circuitpython-adxl36x/)
|
|
4
|
+
[](https://pypi.org/project/circuitpython-adxl36x/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://github.com/mmabey/circuitpython-adxl36x/actions/workflows/ci.yml)
|
|
7
|
+
[](https://pypi.org/project/circuitpython-adxl36x/)
|
|
8
|
+
[](https://github.com/astral-sh/ruff)
|
|
9
|
+
|
|
10
|
+
CircuitPython driver for the Analog Devices [ADXL366](https://www.analog.com/en/products/adxl366.html) and
|
|
11
|
+
[ADXL367](https://www.analog.com/en/products/adxl367.html) ultra-low-power 3-axis MEMS accelerometers.
|
|
12
|
+
|
|
13
|
+
`ADXL367` implements the full shared register map and is the base class. `ADXL366` subclasses it, adding the pedometer,
|
|
14
|
+
Z-axis nonlinearity compensation, and activity/inactivity reference readback - the three features the ADXL367 silicon
|
|
15
|
+
doesn't implement. Both support SPI and I2C.
|
|
16
|
+
|
|
17
|
+
## Relationship to `adafruit_adxl34x`
|
|
18
|
+
|
|
19
|
+
This library's public API deliberately mirrors the shape of [Adafruit's
|
|
20
|
+
`adafruit_adxl34x`](https://github.com/adafruit/Adafruit_CircuitPython_ADXL34x) driver for the ADXL34x family
|
|
21
|
+
(`.acceleration`, `.events`, `enable_tap_detection()`, `enable_motion_detection()`, `data_rate`) so that code written
|
|
22
|
+
against a 34x-family chip needs minimal changes to run against a 366/367. The two chip families are **not**
|
|
23
|
+
register-compatible — only the driver's Python API shape is shared. Notable differences:
|
|
24
|
+
|
|
25
|
+
- `range` is named `g_range` here instead — `range` shadows the Python builtin, and compatibility with the ADXL34x API
|
|
26
|
+
takes a back seat to that on this one property.
|
|
27
|
+
- No `enable_freefall_detection()` — the 366/367 has no freefall-detection hardware.
|
|
28
|
+
- `Range` tops out at `RANGE_8_G` — the 366/367 doesn't support ±16g.
|
|
29
|
+
- `DataRate` only has 6 discrete rates (12.5 Hz-400 Hz) vs. the 34x's 16.
|
|
30
|
+
- `.events` includes many more keys (FIFO, pedometer, ADC/temperature thresholds) since the 366/367 exposes far more
|
|
31
|
+
interrupt sources.
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
On a CircuitPython board, copy `src/adxl36x/` into `CIRCUITPY/lib/adxl36x/`, along with the `adafruit_bus_device` bundle
|
|
36
|
+
library.
|
|
37
|
+
|
|
38
|
+
For host-side development (type checking, tests) in another project:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv add --editable ~/src/circuitpython-adxl36x
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Development
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv sync
|
|
48
|
+
uv run ruff check --fix
|
|
49
|
+
uv run ruff format
|
|
50
|
+
uv run ty check
|
|
51
|
+
uv run pytest
|
|
52
|
+
```
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "circuitpython-adxl36x"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CircuitPython driver for the Analog Devices ADXL366/ADXL367 accelerometers"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
keywords = [
|
|
10
|
+
"adxl366",
|
|
11
|
+
"adxl367",
|
|
12
|
+
"accelerometer",
|
|
13
|
+
"blinka",
|
|
14
|
+
"circuitpython",
|
|
15
|
+
"hardware",
|
|
16
|
+
"i2c",
|
|
17
|
+
"micropython",
|
|
18
|
+
"spi",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
25
|
+
"Topic :: Software Development :: Libraries",
|
|
26
|
+
"Topic :: System :: Hardware",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"adafruit-circuitpython-busdevice>=5.2.11",
|
|
30
|
+
"circuitpython-stubs>=10.2.1",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[[project.authors]]
|
|
34
|
+
name = "Mike Mabey"
|
|
35
|
+
email = "email@mikemabey.com"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/mmabey/circuitpython-adxl36x"
|
|
39
|
+
Repository = "https://github.com/mmabey/circuitpython-adxl36x"
|
|
40
|
+
Issues = "https://github.com/mmabey/circuitpython-adxl36x/issues"
|
|
41
|
+
|
|
42
|
+
[dependency-groups]
|
|
43
|
+
dev = [
|
|
44
|
+
"circup>=1.9.0",
|
|
45
|
+
"pre-commit>=3.5.0",
|
|
46
|
+
"pytest>=8.4.0",
|
|
47
|
+
"ruff>=0.16.1",
|
|
48
|
+
"ty>=0.0.67",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[build-system]
|
|
52
|
+
requires = ["uv_build>=0.12.2,<0.13.0"]
|
|
53
|
+
build-backend = "uv_build"
|
|
54
|
+
|
|
55
|
+
[tool.uv.build-backend]
|
|
56
|
+
module-name = "adxl36x"
|
|
57
|
+
|
|
58
|
+
[tool.ty.src]
|
|
59
|
+
include = [
|
|
60
|
+
"src/adxl36x",
|
|
61
|
+
"tests",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 120
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
extend-select = [
|
|
69
|
+
"A",
|
|
70
|
+
"ANN",
|
|
71
|
+
"ASYNC",
|
|
72
|
+
"B",
|
|
73
|
+
"BLE",
|
|
74
|
+
"C4",
|
|
75
|
+
"C90",
|
|
76
|
+
"COM",
|
|
77
|
+
"DTZ",
|
|
78
|
+
"E",
|
|
79
|
+
"EM",
|
|
80
|
+
"EXE",
|
|
81
|
+
"F",
|
|
82
|
+
"FLY",
|
|
83
|
+
"FURB",
|
|
84
|
+
"I",
|
|
85
|
+
"ICN",
|
|
86
|
+
"INP",
|
|
87
|
+
"ISC",
|
|
88
|
+
"N",
|
|
89
|
+
"PIE",
|
|
90
|
+
"PLC",
|
|
91
|
+
"PLE",
|
|
92
|
+
"PLR",
|
|
93
|
+
"PLW",
|
|
94
|
+
"PT",
|
|
95
|
+
"PTH",
|
|
96
|
+
"PYI",
|
|
97
|
+
"Q",
|
|
98
|
+
"RET",
|
|
99
|
+
"RSE",
|
|
100
|
+
"RUF",
|
|
101
|
+
"S",
|
|
102
|
+
"SIM",
|
|
103
|
+
"T100",
|
|
104
|
+
"TC",
|
|
105
|
+
"TID",
|
|
106
|
+
"TRY",
|
|
107
|
+
"UP",
|
|
108
|
+
"W",
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint.per-file-ignores]
|
|
112
|
+
"tests/**" = [
|
|
113
|
+
"S101",
|
|
114
|
+
"INP001",
|
|
115
|
+
"PLR2004",
|
|
116
|
+
"PLR0913",
|
|
117
|
+
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "circuitpython-adxl36x"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CircuitPython driver for the Analog Devices ADXL366/ADXL367 accelerometers"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Mike Mabey", email = "email@mikemabey.com" },
|
|
11
|
+
]
|
|
12
|
+
keywords = [
|
|
13
|
+
"adxl366",
|
|
14
|
+
"adxl367",
|
|
15
|
+
"accelerometer",
|
|
16
|
+
"blinka",
|
|
17
|
+
"circuitpython",
|
|
18
|
+
"hardware",
|
|
19
|
+
"i2c",
|
|
20
|
+
"micropython",
|
|
21
|
+
"spi",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
28
|
+
"Topic :: Software Development :: Libraries",
|
|
29
|
+
"Topic :: System :: Hardware",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"adafruit-circuitpython-busdevice>=5.2.11",
|
|
33
|
+
"circuitpython-stubs>=10.2.1",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/mmabey/circuitpython-adxl36x"
|
|
38
|
+
Repository = "https://github.com/mmabey/circuitpython-adxl36x"
|
|
39
|
+
Issues = "https://github.com/mmabey/circuitpython-adxl36x/issues"
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"circup>=1.9.0",
|
|
44
|
+
"pre-commit>=3.5.0",
|
|
45
|
+
"pytest>=8.4.0",
|
|
46
|
+
"ruff>=0.16.1",
|
|
47
|
+
"ty>=0.0.67",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["uv_build>=0.12.2,<0.13.0"]
|
|
52
|
+
build-backend = "uv_build"
|
|
53
|
+
|
|
54
|
+
[tool.uv.build-backend]
|
|
55
|
+
module-name = "adxl36x"
|
|
56
|
+
|
|
57
|
+
[tool.ty.src]
|
|
58
|
+
include = ["src/adxl36x", "tests"]
|
|
59
|
+
|
|
60
|
+
[tool.ruff]
|
|
61
|
+
line-length = 120
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint]
|
|
64
|
+
extend-select = [
|
|
65
|
+
"A",
|
|
66
|
+
"ANN",
|
|
67
|
+
"ASYNC",
|
|
68
|
+
"B", # Should be enabled by default
|
|
69
|
+
"BLE",
|
|
70
|
+
"C4",
|
|
71
|
+
"C90",
|
|
72
|
+
"COM",
|
|
73
|
+
"DTZ",
|
|
74
|
+
"E", # Should be enabled by default
|
|
75
|
+
"EM",
|
|
76
|
+
"EXE",
|
|
77
|
+
"F", # Should be enabled by default
|
|
78
|
+
"FLY",
|
|
79
|
+
"FURB",
|
|
80
|
+
"I",
|
|
81
|
+
"ICN",
|
|
82
|
+
"INP",
|
|
83
|
+
"ISC",
|
|
84
|
+
"N",
|
|
85
|
+
"PIE",
|
|
86
|
+
"PLC",
|
|
87
|
+
"PLE",
|
|
88
|
+
"PLR",
|
|
89
|
+
"PLW",
|
|
90
|
+
"PT",
|
|
91
|
+
"PTH",
|
|
92
|
+
"PYI",
|
|
93
|
+
"Q",
|
|
94
|
+
"RET",
|
|
95
|
+
"RSE",
|
|
96
|
+
"RUF", # Should be enabled by default
|
|
97
|
+
"S",
|
|
98
|
+
"SIM",
|
|
99
|
+
"T100",
|
|
100
|
+
"TC",
|
|
101
|
+
"TID",
|
|
102
|
+
"TRY",
|
|
103
|
+
"UP", # Should be enabled by default
|
|
104
|
+
"W",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[tool.ruff.lint.per-file-ignores]
|
|
108
|
+
"tests/**" = ["S101", "INP001", "PLR2004", "PLR0913"]
|
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
"""CircuitPython driver for the Analog Devices ADXL366/ADXL367 accelerometers."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from adafruit_bus_device.i2c_device import I2CDevice
|
|
7
|
+
from adafruit_bus_device.spi_device import SPIDevice
|
|
8
|
+
from micropython import const
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from busio import I2C, SPI
|
|
12
|
+
from digitalio import DigitalInOut
|
|
13
|
+
from typing_extensions import Self
|
|
14
|
+
|
|
15
|
+
_STANDARD_GRAVITY = 9.80665
|
|
16
|
+
|
|
17
|
+
# -- I2C addresses (ASEL pin low/high) --
|
|
18
|
+
_DEFAULT_I2C_ADDRESS = const(0x53)
|
|
19
|
+
_ALT_I2C_ADDRESS = const(0x1D)
|
|
20
|
+
|
|
21
|
+
# -- SPI protocol commands --
|
|
22
|
+
_SPI_WRITE_REG = const(0x0A)
|
|
23
|
+
_SPI_READ_REG = const(0x0B)
|
|
24
|
+
_SPI_READ_FIFO = const(0x0D)
|
|
25
|
+
|
|
26
|
+
# -- Register addresses --
|
|
27
|
+
_REG_DEVID_AD = const(0x00)
|
|
28
|
+
_REG_DEVID_MST = const(0x01)
|
|
29
|
+
_REG_PARTID = const(0x02)
|
|
30
|
+
_REG_REVID = const(0x03)
|
|
31
|
+
_REG_SERIAL_NUMBER_3 = const(0x04)
|
|
32
|
+
_REG_XDATA = const(0x08)
|
|
33
|
+
_REG_STATUS = const(0x0B)
|
|
34
|
+
_REG_FIFO_ENTRIES_L = const(0x0C)
|
|
35
|
+
_REG_FIFO_ENTRIES_H = const(0x0D)
|
|
36
|
+
_REG_XDATA_H = const(0x0E)
|
|
37
|
+
_REG_TEMP_H = const(0x14)
|
|
38
|
+
_REG_EX_ADC_H = const(0x16)
|
|
39
|
+
_REG_I2C_FIFO_DATA = const(0x18)
|
|
40
|
+
_REG_SOFT_RESET = const(0x1F)
|
|
41
|
+
_REG_THRESH_ACT_H = const(0x20)
|
|
42
|
+
_REG_THRESH_ACT_L = const(0x21)
|
|
43
|
+
_REG_TIME_ACT = const(0x22)
|
|
44
|
+
_REG_THRESH_INACT_H = const(0x23)
|
|
45
|
+
_REG_THRESH_INACT_L = const(0x24)
|
|
46
|
+
_REG_TIME_INACT_H = const(0x25)
|
|
47
|
+
_REG_TIME_INACT_L = const(0x26)
|
|
48
|
+
_REG_ACT_INACT_CTL = const(0x27)
|
|
49
|
+
_REG_FIFO_CONTROL = const(0x28)
|
|
50
|
+
_REG_FIFO_SAMPLES = const(0x29)
|
|
51
|
+
_REG_INTMAP1_LWR = const(0x2A)
|
|
52
|
+
_REG_INTMAP2_LWR = const(0x2B)
|
|
53
|
+
_REG_FILTER_CTL = const(0x2C)
|
|
54
|
+
_REG_POWER_CTL = const(0x2D)
|
|
55
|
+
_REG_SELF_TEST = const(0x2E)
|
|
56
|
+
_REG_TAP_THRESH = const(0x2F)
|
|
57
|
+
_REG_TAP_DUR = const(0x30)
|
|
58
|
+
_REG_TAP_LATENT = const(0x31)
|
|
59
|
+
_REG_TAP_WINDOW = const(0x32)
|
|
60
|
+
_REG_X_OFFSET = const(0x33)
|
|
61
|
+
_REG_Y_OFFSET = const(0x34)
|
|
62
|
+
_REG_Z_OFFSET = const(0x35)
|
|
63
|
+
_REG_TIMER_CTL = const(0x39)
|
|
64
|
+
_REG_INTMAP1_UPPER = const(0x3A)
|
|
65
|
+
_REG_INTMAP2_UPPER = const(0x3B)
|
|
66
|
+
_REG_ADC_CTL = const(0x3C)
|
|
67
|
+
_REG_TEMP_CTL = const(0x3D)
|
|
68
|
+
_REG_AXIS_MASK = const(0x43)
|
|
69
|
+
_REG_PEDOMETER_STEP_CNT_H = const(0x47)
|
|
70
|
+
_REG_PEDOMETER_CTL = const(0x49)
|
|
71
|
+
|
|
72
|
+
# -- Device/part identification --
|
|
73
|
+
_DEVID_AD = const(0xAD)
|
|
74
|
+
_DEVID_MST = const(0x1D)
|
|
75
|
+
_PART_ID = const(0xF7)
|
|
76
|
+
_REVID_ADXL367 = const(0x3)
|
|
77
|
+
_REVID_ADXL366 = const(0x5)
|
|
78
|
+
_RESET_KEY = const(0x52)
|
|
79
|
+
|
|
80
|
+
# -- POWER_CTL (0x2D) --
|
|
81
|
+
_POWER_CTL_MODE_MASK = const(0x03)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class OpMode:
|
|
85
|
+
"""Values for `ADXL367.power_mode`."""
|
|
86
|
+
|
|
87
|
+
STANDBY = const(0x00)
|
|
88
|
+
MEASURE = const(0x02)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# -- FILTER_CTL (0x2C) --
|
|
92
|
+
_FILTER_CTL_RANGE_MASK = const(0xC0)
|
|
93
|
+
_FILTER_CTL_RANGE_SHIFT = const(6)
|
|
94
|
+
_FILTER_CTL_ODR_MASK = const(0x07)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Range:
|
|
98
|
+
"""Values for `ADXL367.g_range`.
|
|
99
|
+
|
|
100
|
+
Unlike the ADXL34x family, ±16g isn't supported.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
RANGE_2_G = const(0x00)
|
|
104
|
+
RANGE_4_G = const(0x01)
|
|
105
|
+
RANGE_8_G = const(0x02)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
_RANGE_SCALE_MULTIPLIER = (1, 2, 4) # indexed by Range value
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class DataRate:
|
|
112
|
+
"""Values for `ADXL367.data_rate`."""
|
|
113
|
+
|
|
114
|
+
RATE_12_5_HZ = const(0x00)
|
|
115
|
+
RATE_25_HZ = const(0x01)
|
|
116
|
+
RATE_50_HZ = const(0x02)
|
|
117
|
+
RATE_100_HZ = const(0x03)
|
|
118
|
+
RATE_200_HZ = const(0x04)
|
|
119
|
+
RATE_400_HZ = const(0x05)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
_DATA_RATE_HZ = (12.5, 25.0, 50.0, 100.0, 200.0, 400.0) # indexed by DataRate value
|
|
123
|
+
|
|
124
|
+
# -- Acceleration/temperature scaling --
|
|
125
|
+
_ACCEL_SCALE_MUL = const(245166)
|
|
126
|
+
_ACCEL_SCALE_DIV = const(1_000_000_000)
|
|
127
|
+
_TEMP_OFFSET = const(1185)
|
|
128
|
+
_TEMP_SCALE_MUL = const(18518518)
|
|
129
|
+
_TEMP_SCALE_DIV = const(1_000_000_000)
|
|
130
|
+
|
|
131
|
+
# -- ACT_INACT_CTL (0x27) --
|
|
132
|
+
_ACT_INACT_CTL_ACT_MASK = const(0x03)
|
|
133
|
+
_ACT_INACT_CTL_INACT_MASK = const(0x0C)
|
|
134
|
+
_ACT_INACT_CTL_INACT_SHIFT = const(2)
|
|
135
|
+
_ACT_INACT_CTL_REF_READBACK_MASK = const(0xC0)
|
|
136
|
+
_ACTIVITY_ENABLE = const(0x01)
|
|
137
|
+
_REFERENCED_ACTIVITY_ENABLE = const(0x03)
|
|
138
|
+
_THRESHOLD_MAX = const(0x1FFF)
|
|
139
|
+
|
|
140
|
+
# -- SELF_TEST (0x2E) --
|
|
141
|
+
_SELF_TEST_ST = const(0x01)
|
|
142
|
+
_SELF_TEST_ST_FORCE = const(0x02)
|
|
143
|
+
_SELF_TEST_MIN = const(360) # 90 * 100 / 25
|
|
144
|
+
_SELF_TEST_MAX = const(1080) # 270 * 100 / 25
|
|
145
|
+
|
|
146
|
+
# -- TEMP_CTL (0x3D) / ADC_CTL (0x3C) --
|
|
147
|
+
_TEMP_CTL_EN = const(0x01)
|
|
148
|
+
_TEMP_CTL_NL_COMP_EN = const(0x80)
|
|
149
|
+
_ADC_CTL_EN = const(0x01)
|
|
150
|
+
|
|
151
|
+
# -- Per-axis offset trim (0x33-0x35) --
|
|
152
|
+
_OFFSET_MAX = const(0x1F)
|
|
153
|
+
|
|
154
|
+
# -- FIFO --
|
|
155
|
+
_FIFO_CONTROL_MODE_MASK = const(0x03)
|
|
156
|
+
_FIFO_CONTROL_FORMAT_MASK = const(0x78)
|
|
157
|
+
_FIFO_CONTROL_FORMAT_SHIFT = const(3)
|
|
158
|
+
_FIFO_CONTROL_SAMPLES_MSB = const(0x04)
|
|
159
|
+
_FIFO_ENTRIES_H_MASK = const(0x03)
|
|
160
|
+
_FIFO_SAMPLE_SETS_MAX = const(0x1FF)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class FIFOMode:
|
|
164
|
+
"""Values for `ADXL367.configure_fifo(mode=...)`."""
|
|
165
|
+
|
|
166
|
+
DISABLED = const(0x00)
|
|
167
|
+
OLDEST_SAVED = const(0x01)
|
|
168
|
+
STREAM = const(0x02)
|
|
169
|
+
TRIGGERED = const(0x03)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class FIFOFormat:
|
|
173
|
+
"""Values for `ADXL367.configure_fifo(fifo_format=...)`.
|
|
174
|
+
|
|
175
|
+
T/A suffixes add a temperature-or-ADC sample (whichever is enabled via
|
|
176
|
+
`temperature`/`adc_value`) to each sample set.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
XYZ = const(0x00)
|
|
180
|
+
X = const(0x01)
|
|
181
|
+
Y = const(0x02)
|
|
182
|
+
Z = const(0x03)
|
|
183
|
+
XYZT = const(0x04)
|
|
184
|
+
XT = const(0x05)
|
|
185
|
+
YT = const(0x06)
|
|
186
|
+
ZT = const(0x07)
|
|
187
|
+
XYZA = const(0x08)
|
|
188
|
+
XA = const(0x09)
|
|
189
|
+
YA = const(0x0A)
|
|
190
|
+
ZA = const(0x0B)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
_FIFO_CHANNEL_NAMES = ("x", "y", "z", "temp_or_adc") # indexed by FIFO channel ID
|
|
194
|
+
|
|
195
|
+
# -- Interrupt mapping (INTMAP1/2 LWR + UPPER) --
|
|
196
|
+
_INTMAPX_UPPER_MASK = const(0xDF)
|
|
197
|
+
# name -> (is_upper_byte, bit_index)
|
|
198
|
+
_INTERRUPT_EVENTS = {
|
|
199
|
+
"data_ready": (False, 0),
|
|
200
|
+
"fifo_ready": (False, 1),
|
|
201
|
+
"fifo_watermark": (False, 2),
|
|
202
|
+
"fifo_overrun": (False, 3),
|
|
203
|
+
"activity": (False, 4),
|
|
204
|
+
"inactivity": (False, 5),
|
|
205
|
+
"awake": (False, 6),
|
|
206
|
+
"active_low": (False, 7),
|
|
207
|
+
"single_tap": (True, 0),
|
|
208
|
+
"double_tap": (True, 1),
|
|
209
|
+
"temp_adc_low": (True, 2),
|
|
210
|
+
"temp_adc_high": (True, 3),
|
|
211
|
+
"keep_alive_timer": (True, 4),
|
|
212
|
+
"user_register_error": (True, 6),
|
|
213
|
+
"fuse_error": (True, 7),
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# -- STATUS (0x0B) bits --
|
|
217
|
+
_STATUS_DATA_RDY = const(0x01)
|
|
218
|
+
_STATUS_FIFO_RDY = const(0x02)
|
|
219
|
+
_STATUS_FIFO_WATERMARK = const(0x04)
|
|
220
|
+
_STATUS_FIFO_OVERRUN = const(0x08)
|
|
221
|
+
_STATUS_ACT = const(0x10)
|
|
222
|
+
_STATUS_INACT = const(0x20)
|
|
223
|
+
_STATUS_AWAKE = const(0x40)
|
|
224
|
+
_STATUS_ERR_USER_REGS = const(0x80)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _decode_s14(msb: int, lsb: int) -> int:
|
|
228
|
+
"""Decode a signed 14-bit value from an H/L register pair."""
|
|
229
|
+
value = ((msb << 6) | (lsb >> 2)) & 0x3FFF
|
|
230
|
+
if value & 0x2000:
|
|
231
|
+
value -= 0x4000
|
|
232
|
+
return value
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# -- bus transport --
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class _I2CDevice(I2CDevice):
|
|
239
|
+
"""`I2CDevice` with a correctly-typed `__exit__`.
|
|
240
|
+
|
|
241
|
+
`adafruit_bus_device-stubs` declares `__exit__(self) -> None` (zero
|
|
242
|
+
args), which doesn't match the real 3-argument context-manager protocol
|
|
243
|
+
and makes every `with I2CDevice(...) as ...:` a type error. The real
|
|
244
|
+
`__exit__` only unlocks the bus and always returns a falsy value, so we
|
|
245
|
+
reimplement it directly here instead of delegating to the mistyped
|
|
246
|
+
parent method.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
i2c: "I2C"
|
|
250
|
+
|
|
251
|
+
def __exit__(self, *exc_info: object) -> None:
|
|
252
|
+
self.i2c.unlock()
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class I2CBus:
|
|
256
|
+
"""Register-level access to a device over I2C."""
|
|
257
|
+
|
|
258
|
+
def __init__(self, i2c_bus: "I2C", address: int) -> None:
|
|
259
|
+
self._device = _I2CDevice(i2c_bus, address)
|
|
260
|
+
|
|
261
|
+
def read_into(self, register: int, buffer: bytearray) -> None:
|
|
262
|
+
with self._device as i2c:
|
|
263
|
+
i2c.write_then_readinto(bytes((register,)), buffer)
|
|
264
|
+
|
|
265
|
+
def write(self, register: int, value: int) -> None:
|
|
266
|
+
with self._device as i2c:
|
|
267
|
+
i2c.write(bytes((register, value & 0xFF)))
|
|
268
|
+
|
|
269
|
+
def read_fifo_into(self, buffer: bytearray) -> None:
|
|
270
|
+
with self._device as i2c:
|
|
271
|
+
i2c.write_then_readinto(bytes((_REG_I2C_FIFO_DATA,)), buffer)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class _SPIDevice(SPIDevice):
|
|
275
|
+
"""`SPIDevice` with a correctly-typed `__exit__`. See `_I2CDevice`."""
|
|
276
|
+
|
|
277
|
+
spi: "SPI"
|
|
278
|
+
chip_select: "DigitalInOut | None"
|
|
279
|
+
cs_active_value: bool
|
|
280
|
+
|
|
281
|
+
def __exit__(self, *exc_info: object) -> None:
|
|
282
|
+
# extra_clocks handling is intentionally omitted: we never construct
|
|
283
|
+
# this with a nonzero extra_clocks, so upstream's post-CS clock-out
|
|
284
|
+
# step would always be a no-op here.
|
|
285
|
+
if self.chip_select:
|
|
286
|
+
self.chip_select.value = not self.cs_active_value
|
|
287
|
+
self.spi.unlock()
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class _SPIBus:
|
|
291
|
+
"""Register-level access to a device over SPI."""
|
|
292
|
+
|
|
293
|
+
def __init__(self, spi_bus: "SPI", cs: "DigitalInOut", *, baudrate: int) -> None:
|
|
294
|
+
self._device = _SPIDevice(spi_bus, cs, baudrate=baudrate, polarity=0, phase=0)
|
|
295
|
+
|
|
296
|
+
def read_into(self, register: int, buffer: bytearray) -> None:
|
|
297
|
+
with self._device as spi:
|
|
298
|
+
spi.write(bytes((_SPI_READ_REG, register)))
|
|
299
|
+
spi.readinto(buffer)
|
|
300
|
+
|
|
301
|
+
def write(self, register: int, value: int) -> None:
|
|
302
|
+
with self._device as spi:
|
|
303
|
+
spi.write(bytes((_SPI_WRITE_REG, register, value & 0xFF)))
|
|
304
|
+
|
|
305
|
+
def read_fifo_into(self, buffer: bytearray) -> None:
|
|
306
|
+
with self._device as spi:
|
|
307
|
+
spi.write(bytes((_SPI_READ_FIFO,)))
|
|
308
|
+
spi.readinto(buffer)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class ADXL367:
|
|
312
|
+
"""Driver for the Analog Devices ADXL367 accelerometer.
|
|
313
|
+
|
|
314
|
+
Also the base class for `ADXL366`, which shares this entire register map
|
|
315
|
+
and adds a few extra features. Connect over I2C with the constructor
|
|
316
|
+
(mirroring `adafruit_adxl34x.ADXL345`) or over SPI with `from_spi()`.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
_revid = _REVID_ADXL367
|
|
320
|
+
|
|
321
|
+
def __init__(self, i2c_bus: "I2C", *, address: int = _DEFAULT_I2C_ADDRESS) -> None:
|
|
322
|
+
self._bus: I2CBus | _SPIBus = I2CBus(i2c_bus, address)
|
|
323
|
+
self._range = Range.RANGE_2_G
|
|
324
|
+
self._data_rate = DataRate.RATE_100_HZ
|
|
325
|
+
self._initialize()
|
|
326
|
+
|
|
327
|
+
@classmethod
|
|
328
|
+
def from_spi(
|
|
329
|
+
cls,
|
|
330
|
+
spi_bus: "SPI",
|
|
331
|
+
cs: "DigitalInOut",
|
|
332
|
+
*,
|
|
333
|
+
baudrate: int = 1_000_000,
|
|
334
|
+
) -> "Self":
|
|
335
|
+
"""Construct a driver instance over SPI instead of I2C."""
|
|
336
|
+
self = cls.__new__(cls)
|
|
337
|
+
self._bus = _SPIBus(spi_bus, cs, baudrate=baudrate)
|
|
338
|
+
self._range = Range.RANGE_2_G
|
|
339
|
+
self._data_rate = DataRate.RATE_100_HZ
|
|
340
|
+
self._initialize()
|
|
341
|
+
return self
|
|
342
|
+
|
|
343
|
+
# -- low-level bus access --
|
|
344
|
+
|
|
345
|
+
def _read_into(self, register: int, buffer: bytearray) -> None:
|
|
346
|
+
self._bus.read_into(register, buffer)
|
|
347
|
+
|
|
348
|
+
def _read_u8(self, register: int) -> int:
|
|
349
|
+
buffer = bytearray(1)
|
|
350
|
+
self._read_into(register, buffer)
|
|
351
|
+
return buffer[0]
|
|
352
|
+
|
|
353
|
+
def _write_u8(self, register: int, value: int) -> None:
|
|
354
|
+
self._bus.write(register, value)
|
|
355
|
+
|
|
356
|
+
def _write_masked(self, register: int, value: int, mask: int) -> None:
|
|
357
|
+
current = self._read_u8(register)
|
|
358
|
+
self._write_u8(register, (current & ~mask) | (value & mask))
|
|
359
|
+
|
|
360
|
+
def _read_fifo_into(self, buffer: bytearray) -> None:
|
|
361
|
+
self._bus.read_fifo_into(buffer)
|
|
362
|
+
|
|
363
|
+
def _set_threshold(
|
|
364
|
+
self,
|
|
365
|
+
high_register: int,
|
|
366
|
+
low_register: int,
|
|
367
|
+
threshold: int,
|
|
368
|
+
) -> None:
|
|
369
|
+
if not 0 <= threshold <= _THRESHOLD_MAX:
|
|
370
|
+
msg = f"threshold must be in range 0-{_THRESHOLD_MAX}, got {threshold!r}"
|
|
371
|
+
raise ValueError(msg)
|
|
372
|
+
self._write_u8(high_register, (threshold >> 6) & 0x7F)
|
|
373
|
+
self._write_u8(low_register, (threshold & 0x3F) << 2)
|
|
374
|
+
|
|
375
|
+
# -- initialization --
|
|
376
|
+
|
|
377
|
+
def reset(self) -> None:
|
|
378
|
+
"""Perform a software reset, returning all registers to power-on defaults."""
|
|
379
|
+
self._write_u8(_REG_SOFT_RESET, _RESET_KEY)
|
|
380
|
+
time.sleep(0.02)
|
|
381
|
+
|
|
382
|
+
def _initialize(self) -> None:
|
|
383
|
+
self.reset()
|
|
384
|
+
seen = (
|
|
385
|
+
self._read_u8(_REG_DEVID_AD),
|
|
386
|
+
self._read_u8(_REG_DEVID_MST),
|
|
387
|
+
self._read_u8(_REG_PARTID),
|
|
388
|
+
self._read_u8(_REG_REVID),
|
|
389
|
+
)
|
|
390
|
+
expected = (_DEVID_AD, _DEVID_MST, _PART_ID, self._revid)
|
|
391
|
+
if seen != expected:
|
|
392
|
+
msg = f"{type(self).__name__}: unexpected device ID bytes {seen!r} (expected {expected!r})"
|
|
393
|
+
raise RuntimeError(msg)
|
|
394
|
+
self.power_mode = OpMode.MEASURE
|
|
395
|
+
|
|
396
|
+
# -- identification --
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def serial_number(self) -> int:
|
|
400
|
+
"""This chip's factory-programmed 32-bit unique serial number."""
|
|
401
|
+
buffer = bytearray(4)
|
|
402
|
+
self._read_into(_REG_SERIAL_NUMBER_3, buffer)
|
|
403
|
+
return int.from_bytes(bytes(buffer), "big")
|
|
404
|
+
|
|
405
|
+
# -- power/range/rate --
|
|
406
|
+
|
|
407
|
+
@property
|
|
408
|
+
def power_mode(self) -> int:
|
|
409
|
+
"""The current `OpMode` (standby or measure)."""
|
|
410
|
+
return self._read_u8(_REG_POWER_CTL) & _POWER_CTL_MODE_MASK
|
|
411
|
+
|
|
412
|
+
@power_mode.setter
|
|
413
|
+
def power_mode(self, mode: int) -> None:
|
|
414
|
+
self._write_masked(_REG_POWER_CTL, mode, _POWER_CTL_MODE_MASK)
|
|
415
|
+
|
|
416
|
+
@property
|
|
417
|
+
def g_range(self) -> int:
|
|
418
|
+
"""The measurement range, a `Range` value (tops out at ±8g, unlike ADXL34x)."""
|
|
419
|
+
return self._range
|
|
420
|
+
|
|
421
|
+
@g_range.setter
|
|
422
|
+
def g_range(self, value: int) -> None:
|
|
423
|
+
if value not in (Range.RANGE_2_G, Range.RANGE_4_G, Range.RANGE_8_G):
|
|
424
|
+
msg = f"invalid Range value: {value!r}"
|
|
425
|
+
raise ValueError(msg)
|
|
426
|
+
self._write_masked(
|
|
427
|
+
_REG_FILTER_CTL,
|
|
428
|
+
value << _FILTER_CTL_RANGE_SHIFT,
|
|
429
|
+
_FILTER_CTL_RANGE_MASK,
|
|
430
|
+
)
|
|
431
|
+
self._range = value
|
|
432
|
+
|
|
433
|
+
@property
|
|
434
|
+
def data_rate(self) -> int:
|
|
435
|
+
"""The output data rate, a `DataRate` value."""
|
|
436
|
+
return self._data_rate
|
|
437
|
+
|
|
438
|
+
@data_rate.setter
|
|
439
|
+
def data_rate(self, value: int) -> None:
|
|
440
|
+
if not 0 <= value <= DataRate.RATE_400_HZ:
|
|
441
|
+
msg = f"invalid DataRate value: {value!r}"
|
|
442
|
+
raise ValueError(msg)
|
|
443
|
+
self._write_masked(_REG_FILTER_CTL, value, _FILTER_CTL_ODR_MASK)
|
|
444
|
+
self._data_rate = value
|
|
445
|
+
|
|
446
|
+
# -- acceleration --
|
|
447
|
+
|
|
448
|
+
@property
|
|
449
|
+
def raw_x(self) -> int:
|
|
450
|
+
"""Raw, signed 14-bit X-axis acceleration code."""
|
|
451
|
+
return self._read_raw_axis(_REG_XDATA_H)
|
|
452
|
+
|
|
453
|
+
@property
|
|
454
|
+
def raw_y(self) -> int:
|
|
455
|
+
"""Raw, signed 14-bit Y-axis acceleration code."""
|
|
456
|
+
return self._read_raw_axis(_REG_XDATA_H + 2)
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def raw_z(self) -> int:
|
|
460
|
+
"""Raw, signed 14-bit Z-axis acceleration code."""
|
|
461
|
+
return self._read_raw_axis(_REG_XDATA_H + 4)
|
|
462
|
+
|
|
463
|
+
def _read_raw_axis(self, high_register: int) -> int:
|
|
464
|
+
buffer = bytearray(2)
|
|
465
|
+
self._read_into(high_register, buffer)
|
|
466
|
+
return _decode_s14(buffer[0], buffer[1])
|
|
467
|
+
|
|
468
|
+
@property
|
|
469
|
+
def raw_acceleration(self) -> tuple[int, int, int]:
|
|
470
|
+
"""Raw, signed 14-bit (x, y, z) codes, read in a single bus transaction."""
|
|
471
|
+
buffer = bytearray(6)
|
|
472
|
+
self._read_into(_REG_XDATA_H, buffer)
|
|
473
|
+
return (
|
|
474
|
+
_decode_s14(buffer[0], buffer[1]),
|
|
475
|
+
_decode_s14(buffer[2], buffer[3]),
|
|
476
|
+
_decode_s14(buffer[4], buffer[5]),
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def acceleration(self) -> tuple[float, float, float]:
|
|
481
|
+
"""(x, y, z) acceleration in m/s^2."""
|
|
482
|
+
scale = _RANGE_SCALE_MULTIPLIER[self._range] * _ACCEL_SCALE_MUL / _ACCEL_SCALE_DIV * _STANDARD_GRAVITY
|
|
483
|
+
x, y, z = self.raw_acceleration
|
|
484
|
+
return (x * scale, y * scale, z * scale)
|
|
485
|
+
|
|
486
|
+
# -- offsets --
|
|
487
|
+
|
|
488
|
+
@property
|
|
489
|
+
def offset(self) -> tuple[int, int, int]:
|
|
490
|
+
"""Per-axis trim offsets: raw 5-bit codes (0-31); units unconfirmed."""
|
|
491
|
+
return (
|
|
492
|
+
self._read_u8(_REG_X_OFFSET) & _OFFSET_MAX,
|
|
493
|
+
self._read_u8(_REG_Y_OFFSET) & _OFFSET_MAX,
|
|
494
|
+
self._read_u8(_REG_Z_OFFSET) & _OFFSET_MAX,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
@offset.setter
|
|
498
|
+
def offset(self, value: tuple[int, int, int]) -> None:
|
|
499
|
+
x, y, z = value
|
|
500
|
+
for axis_value in (x, y, z):
|
|
501
|
+
if not 0 <= axis_value <= _OFFSET_MAX:
|
|
502
|
+
msg = f"offset values must be in range 0-{_OFFSET_MAX}, got {value!r}"
|
|
503
|
+
raise ValueError(msg)
|
|
504
|
+
self._write_u8(_REG_X_OFFSET, x)
|
|
505
|
+
self._write_u8(_REG_Y_OFFSET, y)
|
|
506
|
+
self._write_u8(_REG_Z_OFFSET, z)
|
|
507
|
+
|
|
508
|
+
# -- activity/inactivity detection --
|
|
509
|
+
|
|
510
|
+
def enable_motion_detection(
|
|
511
|
+
self,
|
|
512
|
+
*,
|
|
513
|
+
threshold: int = 100,
|
|
514
|
+
time_: int = 1,
|
|
515
|
+
referenced: bool = False,
|
|
516
|
+
) -> None:
|
|
517
|
+
"""Enable activity (motion) detection.
|
|
518
|
+
|
|
519
|
+
`threshold` is a raw accelerometer-code magnitude (same units as
|
|
520
|
+
`raw_acceleration`, i.e. scaled by `range`) rather than the ADXL34x's
|
|
521
|
+
fixed 62.5 mg/LSB - the exact mg/LSB figure for this threshold field
|
|
522
|
+
is not confirmed against the datasheet. `time_` is in seconds
|
|
523
|
+
(unconfirmed exact LSB scale; treat as approximate).
|
|
524
|
+
"""
|
|
525
|
+
self._set_threshold(_REG_THRESH_ACT_H, _REG_THRESH_ACT_L, threshold)
|
|
526
|
+
self._write_u8(_REG_TIME_ACT, time_ & 0xFF)
|
|
527
|
+
mode = _REFERENCED_ACTIVITY_ENABLE if referenced else _ACTIVITY_ENABLE
|
|
528
|
+
self._write_masked(_REG_ACT_INACT_CTL, mode, _ACT_INACT_CTL_ACT_MASK)
|
|
529
|
+
|
|
530
|
+
def disable_motion_detection(self) -> None:
|
|
531
|
+
"""Disable activity (motion) detection."""
|
|
532
|
+
self._write_masked(_REG_ACT_INACT_CTL, 0, _ACT_INACT_CTL_ACT_MASK)
|
|
533
|
+
|
|
534
|
+
def enable_inactivity_detection(
|
|
535
|
+
self,
|
|
536
|
+
*,
|
|
537
|
+
threshold: int = 50,
|
|
538
|
+
time_: int = 3,
|
|
539
|
+
referenced: bool = False,
|
|
540
|
+
) -> None:
|
|
541
|
+
"""Enable inactivity detection.
|
|
542
|
+
|
|
543
|
+
See `enable_motion_detection` for unit caveats.
|
|
544
|
+
"""
|
|
545
|
+
self._set_threshold(_REG_THRESH_INACT_H, _REG_THRESH_INACT_L, threshold)
|
|
546
|
+
self._write_u8(_REG_TIME_INACT_H, (time_ >> 8) & 0xFF)
|
|
547
|
+
self._write_u8(_REG_TIME_INACT_L, time_ & 0xFF)
|
|
548
|
+
mode = _REFERENCED_ACTIVITY_ENABLE if referenced else _ACTIVITY_ENABLE
|
|
549
|
+
shifted = mode << _ACT_INACT_CTL_INACT_SHIFT
|
|
550
|
+
self._write_masked(_REG_ACT_INACT_CTL, shifted, _ACT_INACT_CTL_INACT_MASK)
|
|
551
|
+
|
|
552
|
+
def disable_inactivity_detection(self) -> None:
|
|
553
|
+
"""Disable inactivity detection."""
|
|
554
|
+
self._write_masked(_REG_ACT_INACT_CTL, 0, _ACT_INACT_CTL_INACT_MASK)
|
|
555
|
+
|
|
556
|
+
@property
|
|
557
|
+
def events(self) -> dict[str, bool]:
|
|
558
|
+
"""Currently-set event flags from STATUS (0x0B).
|
|
559
|
+
|
|
560
|
+
Does not yet include tap, pedometer-overflow, or ADC/temperature
|
|
561
|
+
threshold events - their status-register bit positions
|
|
562
|
+
(STATUS_COPY/STATUS_2/STATUS_3) aren't confirmed against the
|
|
563
|
+
datasheet yet.
|
|
564
|
+
"""
|
|
565
|
+
status = self._read_u8(_REG_STATUS)
|
|
566
|
+
return {
|
|
567
|
+
"data_ready": bool(status & _STATUS_DATA_RDY),
|
|
568
|
+
"fifo_ready": bool(status & _STATUS_FIFO_RDY),
|
|
569
|
+
"fifo_watermark": bool(status & _STATUS_FIFO_WATERMARK),
|
|
570
|
+
"fifo_overrun": bool(status & _STATUS_FIFO_OVERRUN),
|
|
571
|
+
"motion": bool(status & _STATUS_ACT),
|
|
572
|
+
"inactivity": bool(status & _STATUS_INACT),
|
|
573
|
+
"awake": bool(status & _STATUS_AWAKE),
|
|
574
|
+
"error": bool(status & _STATUS_ERR_USER_REGS),
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
# -- interrupt routing --
|
|
578
|
+
|
|
579
|
+
def map_interrupt(self, pin: int, events: "set[str] | frozenset[str]") -> None:
|
|
580
|
+
"""Route the named events (keys of `_INTERRUPT_EVENTS`) to INT1 or INT2."""
|
|
581
|
+
if pin not in (1, 2):
|
|
582
|
+
msg = f"pin must be 1 or 2, got {pin!r}"
|
|
583
|
+
raise ValueError(msg)
|
|
584
|
+
lower = upper = 0
|
|
585
|
+
for name in events:
|
|
586
|
+
if name not in _INTERRUPT_EVENTS:
|
|
587
|
+
msg = f"unknown interrupt event: {name!r}"
|
|
588
|
+
raise ValueError(msg)
|
|
589
|
+
is_upper, bit = _INTERRUPT_EVENTS[name]
|
|
590
|
+
if is_upper:
|
|
591
|
+
upper |= 1 << bit
|
|
592
|
+
else:
|
|
593
|
+
lower |= 1 << bit
|
|
594
|
+
lwr_register = _REG_INTMAP1_LWR if pin == 1 else _REG_INTMAP2_LWR
|
|
595
|
+
upper_register = _REG_INTMAP1_UPPER if pin == 1 else _REG_INTMAP2_UPPER
|
|
596
|
+
self._write_u8(lwr_register, lower)
|
|
597
|
+
self._write_u8(upper_register, upper & _INTMAPX_UPPER_MASK)
|
|
598
|
+
|
|
599
|
+
# -- temperature / external ADC --
|
|
600
|
+
|
|
601
|
+
@property
|
|
602
|
+
def temperature(self) -> float:
|
|
603
|
+
"""On-chip temperature in Celsius. Mutually exclusive with `adc_value`."""
|
|
604
|
+
self._write_masked(_REG_TEMP_CTL, _TEMP_CTL_EN, _TEMP_CTL_EN)
|
|
605
|
+
self._write_masked(_REG_ADC_CTL, 0, _ADC_CTL_EN)
|
|
606
|
+
buffer = bytearray(2)
|
|
607
|
+
self._read_into(_REG_TEMP_H, buffer)
|
|
608
|
+
raw = _decode_s14(buffer[0], buffer[1])
|
|
609
|
+
return (raw + _TEMP_OFFSET) * _TEMP_SCALE_MUL / _TEMP_SCALE_DIV
|
|
610
|
+
|
|
611
|
+
@property
|
|
612
|
+
def adc_value(self) -> int:
|
|
613
|
+
"""Raw signed 14-bit ADC code. Mutually exclusive with `temperature`."""
|
|
614
|
+
self._write_masked(_REG_ADC_CTL, _ADC_CTL_EN, _ADC_CTL_EN)
|
|
615
|
+
self._write_masked(_REG_TEMP_CTL, 0, _TEMP_CTL_EN)
|
|
616
|
+
buffer = bytearray(2)
|
|
617
|
+
self._read_into(_REG_EX_ADC_H, buffer)
|
|
618
|
+
return _decode_s14(buffer[0], buffer[1])
|
|
619
|
+
|
|
620
|
+
# -- FIFO --
|
|
621
|
+
|
|
622
|
+
def configure_fifo(
|
|
623
|
+
self,
|
|
624
|
+
*,
|
|
625
|
+
mode: int = FIFOMode.STREAM,
|
|
626
|
+
fifo_format: int = FIFOFormat.XYZ,
|
|
627
|
+
sample_sets: int = 128,
|
|
628
|
+
) -> None:
|
|
629
|
+
"""Configure the FIFO.
|
|
630
|
+
|
|
631
|
+
`sample_sets` (0-511) is the total number of sample sets to store.
|
|
632
|
+
"""
|
|
633
|
+
if not 0 <= sample_sets <= _FIFO_SAMPLE_SETS_MAX:
|
|
634
|
+
msg = f"sample_sets must be in range 0-{_FIFO_SAMPLE_SETS_MAX}, got {sample_sets!r}"
|
|
635
|
+
raise ValueError(msg)
|
|
636
|
+
control = mode & _FIFO_CONTROL_MODE_MASK
|
|
637
|
+
control |= (fifo_format << _FIFO_CONTROL_FORMAT_SHIFT) & _FIFO_CONTROL_FORMAT_MASK
|
|
638
|
+
if sample_sets & 0x100:
|
|
639
|
+
control |= _FIFO_CONTROL_SAMPLES_MSB
|
|
640
|
+
self._write_u8(_REG_FIFO_CONTROL, control)
|
|
641
|
+
self._write_u8(_REG_FIFO_SAMPLES, sample_sets & 0xFF)
|
|
642
|
+
|
|
643
|
+
@property
|
|
644
|
+
def fifo_entries(self) -> int:
|
|
645
|
+
"""Number of 16-bit words currently stored in the FIFO."""
|
|
646
|
+
low = self._read_u8(_REG_FIFO_ENTRIES_L)
|
|
647
|
+
high = self._read_u8(_REG_FIFO_ENTRIES_H) & _FIFO_ENTRIES_H_MASK
|
|
648
|
+
return (high << 8) | low
|
|
649
|
+
|
|
650
|
+
def read_fifo(self) -> list[tuple[str, int]]:
|
|
651
|
+
"""Read and drain all available FIFO entries.
|
|
652
|
+
|
|
653
|
+
Returns a list of (channel_name, raw_signed_code) pairs, where
|
|
654
|
+
channel_name is one of "x", "y", "z", "temp_or_adc". Accel-channel
|
|
655
|
+
values use the same raw-code scale as `raw_acceleration`.
|
|
656
|
+
"""
|
|
657
|
+
entries = self.fifo_entries
|
|
658
|
+
buffer = bytearray(entries * 2)
|
|
659
|
+
self._read_fifo_into(buffer)
|
|
660
|
+
result = []
|
|
661
|
+
for i in range(entries):
|
|
662
|
+
high, low = buffer[2 * i], buffer[2 * i + 1]
|
|
663
|
+
channel = high >> 6
|
|
664
|
+
raw = ((high & 0x3F) << 8) | low
|
|
665
|
+
if raw & 0x2000:
|
|
666
|
+
raw -= 0x4000
|
|
667
|
+
result.append((_FIFO_CHANNEL_NAMES[channel], raw))
|
|
668
|
+
return result
|
|
669
|
+
|
|
670
|
+
# -- self-test --
|
|
671
|
+
|
|
672
|
+
def self_test(self) -> bool:
|
|
673
|
+
"""Run the built-in electrostatic self-test. Returns True if it passes."""
|
|
674
|
+
previous_mode = self.power_mode
|
|
675
|
+
self.power_mode = OpMode.MEASURE
|
|
676
|
+
delay = 4.0 / _DATA_RATE_HZ[self._data_rate]
|
|
677
|
+
both_bits = _SELF_TEST_ST | _SELF_TEST_ST_FORCE
|
|
678
|
+
try:
|
|
679
|
+
self._write_masked(_REG_SELF_TEST, _SELF_TEST_ST, both_bits)
|
|
680
|
+
time.sleep(delay)
|
|
681
|
+
before = self.raw_x
|
|
682
|
+
self._write_masked(_REG_SELF_TEST, both_bits, both_bits)
|
|
683
|
+
time.sleep(delay)
|
|
684
|
+
after = self.raw_x
|
|
685
|
+
finally:
|
|
686
|
+
self._write_u8(_REG_SELF_TEST, 0)
|
|
687
|
+
self.power_mode = previous_mode
|
|
688
|
+
diff = after - before
|
|
689
|
+
scale = _RANGE_SCALE_MULTIPLIER[self._range]
|
|
690
|
+
return _SELF_TEST_MIN * scale <= diff <= _SELF_TEST_MAX * scale
|
|
691
|
+
|
|
692
|
+
|
|
693
|
+
class ADXL366(ADXL367):
|
|
694
|
+
"""Driver for the Analog Devices ADXL366 accelerometer.
|
|
695
|
+
|
|
696
|
+
Adds Z-axis nonlinearity compensation, activity/inactivity reference
|
|
697
|
+
readback, and a step-counting pedometer on top of everything `ADXL367`
|
|
698
|
+
provides - the ADXL367 silicon doesn't implement these three features.
|
|
699
|
+
"""
|
|
700
|
+
|
|
701
|
+
_revid = _REVID_ADXL366
|
|
702
|
+
|
|
703
|
+
@property
|
|
704
|
+
def z_nonlinearity_compensation(self) -> bool:
|
|
705
|
+
"""Z-axis temperature-informed nonlinearity compensation state."""
|
|
706
|
+
return bool(self._read_u8(_REG_TEMP_CTL) & _TEMP_CTL_NL_COMP_EN)
|
|
707
|
+
|
|
708
|
+
@z_nonlinearity_compensation.setter
|
|
709
|
+
def z_nonlinearity_compensation(self, enable: bool) -> None:
|
|
710
|
+
value = _TEMP_CTL_NL_COMP_EN if enable else 0
|
|
711
|
+
self._write_masked(_REG_TEMP_CTL, value, _TEMP_CTL_NL_COMP_EN)
|
|
712
|
+
|
|
713
|
+
def reference_readback(self, *, inactivity: bool = False) -> tuple[int, int, int]:
|
|
714
|
+
"""Read back the activity/inactivity reference point.
|
|
715
|
+
|
|
716
|
+
Only meaningful when the corresponding detector is in referenced mode.
|
|
717
|
+
"""
|
|
718
|
+
select = 0x02 if inactivity else 0x01
|
|
719
|
+
self._write_masked(
|
|
720
|
+
_REG_ACT_INACT_CTL,
|
|
721
|
+
select << 6,
|
|
722
|
+
_ACT_INACT_CTL_REF_READBACK_MASK,
|
|
723
|
+
)
|
|
724
|
+
try:
|
|
725
|
+
return self.raw_acceleration
|
|
726
|
+
finally:
|
|
727
|
+
self._write_masked(_REG_ACT_INACT_CTL, 0, _ACT_INACT_CTL_REF_READBACK_MASK)
|
|
728
|
+
|
|
729
|
+
@property
|
|
730
|
+
def pedometer_enabled(self) -> bool:
|
|
731
|
+
"""Whether the step-counting pedometer is enabled."""
|
|
732
|
+
return bool(self._read_u8(_REG_PEDOMETER_CTL) & 0x01)
|
|
733
|
+
|
|
734
|
+
@pedometer_enabled.setter
|
|
735
|
+
def pedometer_enabled(self, enable: bool) -> None:
|
|
736
|
+
self._write_masked(_REG_PEDOMETER_CTL, 0x01 if enable else 0x00, 0x01)
|
|
737
|
+
|
|
738
|
+
@property
|
|
739
|
+
def steps(self) -> int:
|
|
740
|
+
"""Cumulative pedometer step count."""
|
|
741
|
+
buffer = bytearray(2)
|
|
742
|
+
self._read_into(_REG_PEDOMETER_STEP_CNT_H, buffer)
|
|
743
|
+
return (buffer[0] << 8) | buffer[1]
|
|
744
|
+
|
|
745
|
+
def reset_steps(self) -> None:
|
|
746
|
+
"""Reset the pedometer step count to zero."""
|
|
747
|
+
self._write_masked(_REG_PEDOMETER_CTL, 0x04, 0x04)
|
|
File without changes
|