mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 18:48:41 +00:00
Compare commits
551 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc6edbb694 | |||
| 1d695a231a | |||
| 04acd8e666 | |||
| bb2d365868 | |||
| f459992f3c | |||
| 62663c1832 | |||
| 8f372935ac | |||
| fb3062433a | |||
| 9fc1c0aedc | |||
| 1c10dea443 | |||
| e960a6ff57 | |||
| 2673cc1e9a | |||
| 0034fc511a | |||
| a0c5da601f | |||
| 21a7d0a96a | |||
| e232a4f136 | |||
| 4071028e85 | |||
| a514169c37 | |||
| b480978e1a | |||
| 9eb95a4651 | |||
| d6114c9f6d | |||
| 0e2fb829ef | |||
| 892265b733 | |||
| b390e3225a | |||
| 1513808425 | |||
| 0a41786a77 | |||
| c21cd39813 | |||
| a35786be70 | |||
| 2a42fa7cbb | |||
| 5d96a56603 | |||
| f931ad44fb | |||
| 50778af656 | |||
| 936a255df9 | |||
| 9ed648f4a5 | |||
| fab688a701 | |||
| d4654668f2 | |||
| 253f8e3220 | |||
| c710cc8236 | |||
| 4b831af52b | |||
| 09fd01340b | |||
| 23833e69c7 | |||
| 744fda5eec | |||
| 027c91272e | |||
| 5847506ba6 | |||
| e35e6b8e13 | |||
| 408851a9f5 | |||
| bd88b70511 | |||
| 34da2d208a | |||
| 5cb360a4bf | |||
| 51b604ce97 | |||
| ec680be784 | |||
| 5fb935001e | |||
| 297ad416d3 | |||
| 3fa08112db | |||
| b639f7b12e | |||
| 28d625abfd | |||
| 228e57cf98 | |||
| 6cf1eb86e5 | |||
| 857ab80ae6 | |||
| a31ba26a6c | |||
| 303ec94529 | |||
| 833990f44d | |||
| 28d87eb0c5 | |||
| ae9e8f915e | |||
| 45fad80c3e | |||
| 0304d30b78 | |||
| 7de0a0814e | |||
| 80356fd9d9 | |||
| c4ff8242a7 | |||
| 82c41dbf14 | |||
| c4168864fd | |||
| dbc9022f6a | |||
| d41bc0b85f | |||
| e7f90166ad | |||
| 156d6e7c9c | |||
| 6e04decf19 | |||
| afb1ea39c2 | |||
| 382e79a748 | |||
| 06dd60fba2 | |||
| 27a7d5031e | |||
| 6281f36662 | |||
| ccfcfa837e | |||
| 87c205fd65 | |||
| f57b5ecee9 | |||
| 486949686c | |||
| 00578a3a81 | |||
| 17f273243d | |||
| 0623bb425c | |||
| 019bbbe5fb | |||
| d04e68bef8 | |||
| 76a4276b5d | |||
| 7d7efe25e7 | |||
| 0186d0905d | |||
| 2f3b5ca59a | |||
| ecae5e28ad | |||
| bcf3b84654 | |||
| 4fb5d7a0e5 | |||
| f12454d4f7 | |||
| 829ae09651 | |||
| 30f540c29f | |||
| 6d431e0726 | |||
| f308f8fce0 | |||
| 1c2b802f47 | |||
| ce80e7582f | |||
| 109cab4f3b | |||
| 07216d3051 | |||
| d688fa3f3f | |||
| dd44f9220b | |||
| ee3842f639 | |||
| 1bf1a11067 | |||
| 848f5def03 | |||
| 4a0bcba87c | |||
| 7d983647ad | |||
| 831982ad33 | |||
| 9063f46313 | |||
| 2a621dc3b1 | |||
| 7dbca2a543 | |||
| 50b6f18cc2 | |||
| bb0e6b8ba8 | |||
| 5eeba138a4 | |||
| 1095629f62 | |||
| bf0b383176 | |||
| f206f6f56f | |||
| a0293643b3 | |||
| 60801ba852 | |||
| a74770b2e6 | |||
| 3ed09d3fe1 | |||
| 22581dfd58 | |||
| 31f09d93b8 | |||
| 0ea969f25a | |||
| 8665d44e0f | |||
| c344e5d879 | |||
| 308763f500 | |||
| c41f2e472d | |||
| aa7bd47c68 | |||
| e7931c6a22 | |||
| 3c5e17423a | |||
| 0262122ccd | |||
| 7d24c3c285 | |||
| 5c0f0480bc | |||
| 8642886f73 | |||
| 1a96fc4547 | |||
| 3fabdc5c96 | |||
| a8928d137e | |||
| 709fc77960 | |||
| d6ae0b0d8b | |||
| 7a7235f83b | |||
| 3089bf8b1b | |||
| 196d1dd58d | |||
| 1fd8a768ae | |||
| 8238f96319 | |||
| e7d51d3c73 | |||
| 4bf631a7e3 | |||
| 3c4df6e811 | |||
| 255c639ad4 | |||
| 276feecc20 | |||
| 00b1b4fbfb | |||
| 3b183ad6ed | |||
| 68da68c8c8 | |||
| f7b80ef95a | |||
| 933c9e127d | |||
| 92050d90da | |||
| 93cbba5d8b | |||
| 6cf07aff4c | |||
| af33129f41 | |||
| 100aadf585 | |||
| 4a9667ffef | |||
| f96a70915e | |||
| 3e64e754a2 | |||
| 2c2da5c7bb | |||
| 14994a3f31 | |||
| 47dfeb9e94 | |||
| f20d1759d3 | |||
| 6b79a1b386 | |||
| 7dd1067cc1 | |||
| 4edb771c5b | |||
| e95cfe66c1 | |||
| 4edb58c0c5 | |||
| f384e2db48 | |||
| 7c96387845 | |||
| 182d4c6ebb | |||
| dc2de4f9ed | |||
| 36a12dd7a2 | |||
| 00992756eb | |||
| 715c33d4b0 | |||
| fd21e6ac9b | |||
| 485ed702c8 | |||
| cfa50fd3c2 | |||
| 5888bb2ded | |||
| a9b36f8bd4 | |||
| 1e4545828f | |||
| cfa5103969 | |||
| a45227590e | |||
| 4f789fb556 | |||
| 996f1b047f | |||
| eef03917ab | |||
| 31538f1b2f | |||
| 64029612e0 | |||
| e6f82fad2e | |||
| e20c6d05f8 | |||
| 9c29529cbb | |||
| f6d399ec08 | |||
| 5e0191655b | |||
| 95f0ca8c3e | |||
| 42e4d29157 | |||
| f627fd162d | |||
| f3f2d65878 | |||
| deca190ba2 | |||
| 184dff93b1 | |||
| 331a60ce2d | |||
| de2742f10c | |||
| 422051d822 | |||
| 66abe9c28b | |||
| 5f25a4f6be | |||
| 9955e09466 | |||
| bc9177726d | |||
| aac959eb7b | |||
| 9fdf0d657d | |||
| b0c9f259dc | |||
| 53404b3f4b | |||
| 8ddfa6ac8f | |||
| c0ab3596fa | |||
| 9de76bc145 | |||
| ae04d5b0bb | |||
| 3c01b7e222 | |||
| c689c11838 | |||
| 59d66d1e73 | |||
| 1eb7072162 | |||
| 5cc21329a6 | |||
| d3022e7d6e | |||
| 5c06b82b27 | |||
| bf53cb2fd4 | |||
| 1fd38a8a68 | |||
| 55e100c4fe | |||
| cba17ef125 | |||
| c59b5a909b | |||
| b0a971573b | |||
| 94017b5437 | |||
| 56208a28d5 | |||
| 0adc7e78aa | |||
| 820298bfe3 | |||
| 46cbacbe03 | |||
| 9d4847fc6d | |||
| 4de0afc224 | |||
| 21fa184b76 | |||
| fae4307190 | |||
| 7f61bda24c | |||
| e613ab1eb3 | |||
| d57d9b6c74 | |||
| 07ba9ec0fd | |||
| fa0cf6ec7f | |||
| 4e687b29c3 | |||
| d283abdb3a | |||
| 02d808a6fb | |||
| d473b9e1dc | |||
| 7cc32dffed | |||
| 58cb44f3f6 | |||
| 78cc1afe46 | |||
| e6cde8f7ae | |||
| 89f113c0ca | |||
| 3b8a808a52 | |||
| 7506a895a2 | |||
| 4a950a4474 | |||
| f2637a87d2 | |||
| ecb343b3b3 | |||
| 0b62d4fb4a | |||
| 5b461ec2d1 | |||
| b1c9612158 | |||
| 0b6e1d0e78 | |||
| 49e7a6d6c8 | |||
| be644b36fe | |||
| 409723e496 | |||
| 8aa3530c8b | |||
| 390854ee45 | |||
| ce5dd65dad | |||
| 73e9d5bfec | |||
| 462d2b3315 | |||
| 9e70ecbe64 | |||
| d6d06396b4 | |||
| 80b2a4a569 | |||
| 556aa9e6d3 | |||
| 4bc8ecdbdd | |||
| 999f42dc03 | |||
| a1b47cd778 | |||
| 889536f7f0 | |||
| 301e6bc35b | |||
| 49b390f7bc | |||
| a62fc01d81 | |||
| 36dfdf568e | |||
| 7b5a3970ca | |||
| 3433364586 | |||
| 338be928c9 | |||
| 98943393a5 | |||
| 6b817faed3 | |||
| 9b14ee3ec5 | |||
| 6d51370512 | |||
| 2005d7d483 | |||
| f15eec822f | |||
| dc4f0c974a | |||
| 28ddb2681d | |||
| 877342d0ff | |||
| 8b3075fdba | |||
| 7d58709b7d | |||
| 789bbbd89f | |||
| ea944c8933 | |||
| d27859453c | |||
| 0ccd979f23 | |||
| a888570c8c | |||
| 5b2c2f800d | |||
| 0ec887c9d8 | |||
| c5dbe18be1 | |||
| 2bca84b8d1 | |||
| 690daebcd1 | |||
| 89770a7d05 | |||
| 8a39bb7aae | |||
| a106fd48ce | |||
| b2fdd3c333 | |||
| 42dc6eb068 | |||
| c00cb3abdd | |||
| 81bc1bcad1 | |||
| 1bb1b621c6 | |||
| 98ba5a231a | |||
| 5af2512901 | |||
| 4061adf6db | |||
| 7133c2e9db | |||
| 0ea72e7f1e | |||
| 7396a5be43 | |||
| c409e8f3cf | |||
| a6a8d478b9 | |||
| 54ddb895cc | |||
| 6f05615f3e | |||
| 2a2e897261 | |||
| ce0ce03cfe | |||
| 5bd814c9de | |||
| 41b7f06d05 | |||
| 749b2f6726 | |||
| 71c77d4b53 | |||
| 1f54c7dd23 | |||
| d0163c67f5 | |||
| 80cd4d9dac | |||
| 594ba1dab2 | |||
| 625c565c0b | |||
| b64d4ec3a0 | |||
| 171366698b | |||
| ca34b93e5d | |||
| 2cfc08b560 | |||
| 1850c5effb | |||
| 810888ce42 | |||
| cafe8df620 | |||
| 13c43350a3 | |||
| 3edd8e413b | |||
| 159f13974c | |||
| 8ddcd69ac0 | |||
| 1c038f5183 | |||
| 36a8b791dc | |||
| df0d0d29a4 | |||
| 3bdab45452 | |||
| 9f6783670f | |||
| ec00fdef9b | |||
| e4951091f4 | |||
| 19caee9b76 | |||
| c235509580 | |||
| dbff576c9e | |||
| f0a260be66 | |||
| 06e298c514 | |||
| f2f470973d | |||
| 448598cbf8 | |||
| ec98f2dc2c | |||
| 2476cd7bd8 | |||
| 01fed475e1 | |||
| edfdd4e09e | |||
| 5e54f08605 | |||
| 880b958d64 | |||
| 24db8b4e2c | |||
| 6df9247f92 | |||
| 56fb346438 | |||
| 3d279d4d25 | |||
| 4a336f674c | |||
| 2d32995f6a | |||
| 9a98d1be55 | |||
| 02e4548966 | |||
| 45dce188f5 | |||
| b3c0315a09 | |||
| 0ced12683c | |||
| b4eb406a43 | |||
| 10bd48c39d | |||
| ad3ef92cfe | |||
| f7dce6ae22 | |||
| a15f2167cc | |||
| 5d5378a47c | |||
| 3ceb688663 | |||
| 941c1c9057 | |||
| d8c813d515 | |||
| 905fc6fce3 | |||
| 80913d5fe7 | |||
| b6c750ccd1 | |||
| 43899e1330 | |||
| 45c68ef0fc | |||
| 025a66655f | |||
| 121e8147f7 | |||
| b48bc5ac5d | |||
| 0d40c99d1f | |||
| bee7422c3a | |||
| 43099c5d5f | |||
| 0f9b9d873b | |||
| 114e24870e | |||
| b33c84ff78 | |||
| 966210f1b0 | |||
| 90a13a697b | |||
| d632eb888f | |||
| f6761a9079 | |||
| 51c3aa271b | |||
| 520234e9f8 | |||
| 334ed57373 | |||
| 3637033fec | |||
| 233866443c | |||
| 65fd7aa1a1 | |||
| 82e9e4a7f3 | |||
| 76ff632134 | |||
| cd2694e85f | |||
| 734adb6df8 | |||
| 2492fcb322 | |||
| 4f82c231cb | |||
| 2c9a05a413 | |||
| a89a26112e | |||
| bd42656644 | |||
| 94729a4f7d | |||
| 050cf4dbbc | |||
| 49c8810432 | |||
| 4b38841f71 | |||
| 3fe3fc64e4 | |||
| 0565a15b17 | |||
| fad9d12e0b | |||
| f2e5278965 | |||
| 259593e608 | |||
| 7a2b1b8c90 | |||
| cad03a7d4e | |||
| 98f5396625 | |||
| 3b16972ed8 | |||
| b35496dd1b | |||
| ad6be40966 | |||
| b3af3f594e | |||
| 9a3bfd4826 | |||
| 1ba9ba3143 | |||
| feb1c63656 | |||
| 0691ed5205 | |||
| e70beede1c | |||
| 92673d996d | |||
| 374e2dfe1d | |||
| b35aab6d61 | |||
| 358501fc47 | |||
| a0119ffc6f | |||
| 58106b5942 | |||
| 2537c69b0f | |||
| 633f19c4ac | |||
| 6a4cd9d8e7 | |||
| 8a4a0ff017 | |||
| 1cbf1a6f3d | |||
| 001bdb43ba | |||
| f7ae6fac6d | |||
| a575b2a4ed | |||
| 0bc3702dd0 | |||
| b53a8a17e7 | |||
| 5e117c162c | |||
| 24dfd8795a | |||
| a748451cab | |||
| a811483fd4 | |||
| 7b565e8cb0 | |||
| 5645eb3f91 | |||
| 6b914f7af7 | |||
| 5a02fe068b | |||
| 5ecefde991 | |||
| 9adbde99a2 | |||
| a09ec31e7a | |||
| d07ad40373 | |||
| aa17ffd1cb | |||
| ce03bebc34 | |||
| dfd652ca1f | |||
| c765ef3970 | |||
| 428db3cd12 | |||
| d7f619ca21 | |||
| d1ace44754 | |||
| 6b58fdc95a | |||
| bd2530abee | |||
| 3bcde1485c | |||
| 351700e48d | |||
| cbcb62af01 | |||
| 19bb773e5b | |||
| 69cc0b22c4 | |||
| 2a48b5c25d | |||
| e6a822f68b | |||
| f65bce6d36 | |||
| 67b8a341a6 | |||
| b869d27170 | |||
| 107932a201 | |||
| d2db0c28e2 | |||
| 761bcfc4db | |||
| 91dadd5535 | |||
| f7dc106fc8 | |||
| df343190c2 | |||
| 6bc466f79b | |||
| 23776bf906 | |||
| e77e9249cd | |||
| 1345bc2161 | |||
| ab795cc186 | |||
| 36213a1cdc | |||
| 963c903d71 | |||
| 2cec480fb8 | |||
| 68da7f437b | |||
| d0632fccfa | |||
| 2c0a74df4d | |||
| c6228a0677 | |||
| 33ec7fc98f | |||
| 75c15d5d0b | |||
| e1deb45aac | |||
| d1cafbc2b2 | |||
| 0b6bfda8cf | |||
| 84408279de | |||
| de1e6a626a | |||
| 7531c9d334 | |||
| ee44900c25 | |||
| b9a70aeb6f | |||
| cad0a320b7 | |||
| a53a39922b | |||
| 41d6a4c3fa | |||
| 60ba0e3b30 | |||
| 2b5f43029c | |||
| a771641339 | |||
| 5741ceb9d1 | |||
| 27ef18930e | |||
| e9a6d96ddd | |||
| 9a7cb1a22f | |||
| d170ec8d81 | |||
| 0fc2d28ca8 | |||
| ef34c13cc1 | |||
| c64d7920dc | |||
| 008157b6c9 | |||
| 0ed9683a52 | |||
| 01acd977f3 | |||
| 231ec57202 | |||
| 5d3ad4ba52 | |||
| 8cb226938b | |||
| ce5e443084 | |||
| edf9ba92be | |||
| c1c05cbef7 | |||
| c338348d2b | |||
| b6aa674b2a | |||
| cc7079b0cd | |||
| 42a907035b | |||
| 46c9ba9595 | |||
| df64ce8f50 |
@@ -1,17 +0,0 @@
|
||||
# Golang CircleCI 2.0 configuration file
|
||||
#
|
||||
# Check https://circleci.com/docs/2.0/language-go/ for more details
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: tinygo/tinygo-dev
|
||||
steps:
|
||||
- checkout
|
||||
- run: tinygo version
|
||||
- run:
|
||||
name: "Enforce Go Formatted Code"
|
||||
command: make fmt-check
|
||||
- run:
|
||||
name: "Run build and smoke tests"
|
||||
command: make smoke-test
|
||||
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
open_collective: tinygo
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- release
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/tinygo-org/tinygo:latest
|
||||
options: --user root
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: TinyGo version check
|
||||
run: tinygo version
|
||||
- name: Enforce Go Formatted Code
|
||||
run: make fmt-check
|
||||
- name: Run unit tests
|
||||
run: make unit-test
|
||||
- name: Run build and smoke tests
|
||||
run: |
|
||||
go env -w GOFLAGS=-buildvcs=false
|
||||
make smoke-test
|
||||
@@ -1 +1,2 @@
|
||||
build
|
||||
.vscode/
|
||||
|
||||
+802
@@ -1,3 +1,805 @@
|
||||
0.35.0
|
||||
---
|
||||
- **new devices**
|
||||
- **unoqmatrix**
|
||||
- LED matrix on the Arduino Uno Q
|
||||
- **waveshare-epd (ssd1680)**
|
||||
- Add driver for Waveshare 2.9 inch v2 e-paper display
|
||||
|
||||
- **enhancements**
|
||||
- **gps**
|
||||
- add UBX config command support (#831)
|
||||
- improve implementation for UBX config commands
|
||||
- revamp validSentence() to avoid heap allocation for errors
|
||||
- export some errors for checking/suppression from client
|
||||
- improvements and corrections for config commands
|
||||
- **lora**
|
||||
- fill out more constants for lora device
|
||||
- **mcp2515**
|
||||
- add support for extended CAN IDs (#857)
|
||||
- **si5351**
|
||||
- complete refactor for more complete interface
|
||||
- **st7735**
|
||||
- remove dependency on the machine package
|
||||
- **sx127x**
|
||||
- add functions used for FSK radio communication
|
||||
- **ws2812**
|
||||
- add brightness control
|
||||
- add PIO support for RP2040/RP2350
|
||||
|
||||
- **bugfixes**
|
||||
- **st7789**
|
||||
- fix scroll on rotated displays
|
||||
- fix driver when rotated 90º
|
||||
- **ws2812**
|
||||
- fix brightness control issues (#858)
|
||||
|
||||
|
||||
0.34.0
|
||||
---
|
||||
- **core**
|
||||
- add regmap package to facilitate heapless driver development
|
||||
- PinInput+PinOutput HAL (#753, reloaded) (#795)
|
||||
- Add Device8I2C/SPI types and their logic (#801)
|
||||
|
||||
- **new devices**
|
||||
- **bno8x**
|
||||
- Add support for CEVA BNO08x 9DoF sensor (#809)
|
||||
- **hineyhsc**
|
||||
- Add Honeywell HSC TruStability SPI+I2C pressure sensor driver (#799)
|
||||
- **p25q16h**
|
||||
- added support for P25Q16H flash chip for xiao-ble target
|
||||
- **si5351**
|
||||
- add support for si5351 (#810)
|
||||
- **w25q80dv**
|
||||
- added support for W25Q80DV flash chip for xiao-ble target
|
||||
- **w5500**
|
||||
- initial version the driver (#788)
|
||||
|
||||
- **enhancements**
|
||||
- **ds3231**
|
||||
- DS3231 Alarm features (#805)
|
||||
- **general**
|
||||
- add simplest driver ports
|
||||
- **lis3dh**
|
||||
- add Update and Acceleration calls
|
||||
- use correct error handling and make configurable
|
||||
- **lsm9ds1**
|
||||
- avoid unnecessary heap allocations
|
||||
- **pixel**
|
||||
- add Grayscale2bit color (#817)
|
||||
- **scd4x**
|
||||
- add support for SCD41 single-shot measurements
|
||||
- remove dead code
|
||||
- update package to use standard methods
|
||||
- **si5351**
|
||||
- add many missing functions needed for convenient use.
|
||||
- **ssd1xxx**
|
||||
- break dependency from machine package (#812)
|
||||
- **test**
|
||||
- Add TestImageRGB888 and TestImageRGB555
|
||||
|
||||
- **bugfixes**
|
||||
- **quadrature**
|
||||
- add RP2350 to quadrature_interrupt.go
|
||||
- **pixel**
|
||||
- correct logic error in image size checks in pixel's tests
|
||||
- correct logic error in image size checks in pixel's tests (Monochrome)
|
||||
- correct RGB555 to RGBA conversion logic
|
||||
|
||||
|
||||
0.33.0
|
||||
---
|
||||
- **new devices**
|
||||
- **ens160**
|
||||
- Add ens160 i2c driver
|
||||
- **lsm303dlhc**
|
||||
- added support for LSM303DLHC e-Compass; (#783)
|
||||
- **seesaw**
|
||||
- add support for Adafruit Seesaw encoders
|
||||
|
||||
- **enhancements**
|
||||
- **ws2812**
|
||||
- add RP2350 support
|
||||
- **ssd1306**
|
||||
- avoid unnecessary heap allocations (#767)
|
||||
- **gps**
|
||||
- allow gps init with address
|
||||
- **lsm6ds3tr**
|
||||
- avoid unnecessary heap allocations (#766)
|
||||
|
||||
- **bugfixes**
|
||||
- **gps**
|
||||
- Fix gps time calculation (#785)
|
||||
|
||||
|
||||
0.32.0
|
||||
---
|
||||
- **enhancements**
|
||||
- **bmp280**
|
||||
- remove alloc on read sensor data
|
||||
- **ws2812**
|
||||
- add 200MHz support for the Cortex-M0/rp2040
|
||||
|
||||
- **bugfixes**
|
||||
- **ssd1306**
|
||||
- remove time.Sleep from SSD1306 SPI transfer code
|
||||
- **tmc2209**
|
||||
- tmc2209 bug fixes (#755)
|
||||
|
||||
- **docs**
|
||||
- **contributing**
|
||||
- add driver design pointer to CONTRIBUTING.md
|
||||
|
||||
|
||||
0.31.0
|
||||
---
|
||||
---
|
||||
- **enhancements**
|
||||
- **spi**
|
||||
- update all SPI usage to use either *machine.SPI or drivers.SPI
|
||||
|
||||
|
||||
0.30.0
|
||||
---
|
||||
- **new devices**
|
||||
- **comboat**
|
||||
- Add wifi driver comboat for Elecrow W5 rp2040 and rp2350 devices (#741)
|
||||
- **max6675**
|
||||
- Add MAX6675 device
|
||||
- **TMC2209**
|
||||
- Added TMC2209 support (#727)
|
||||
- **TMC5160**
|
||||
- Added TMC5160 support (#725)
|
||||
- **sharpmem**
|
||||
- Add sharpmem (#724)
|
||||
|
||||
- **enhancements**
|
||||
- **net**
|
||||
- move to latest golang.org/x/net v0.33.0 (#732)
|
||||
- **microphone**
|
||||
- update microphone driver to use latest i2s interface
|
||||
|
||||
- **bugfixes**
|
||||
- **net**
|
||||
- fix typo in DHCP error message
|
||||
- **aht20**
|
||||
- Fixed bug in aht20 driver
|
||||
- **hub75**
|
||||
- fix data buffering
|
||||
|
||||
|
||||
0.29.0
|
||||
---
|
||||
- **new devices**
|
||||
- **epd1in54**
|
||||
- Waveshare 1.54inch B/W e-Paper display (#704)
|
||||
- **touch**
|
||||
- add capacitive touch sensing on normal GPIO pins
|
||||
- **INA219**
|
||||
- I2C INA219 driver (#705)
|
||||
- **pcf8591**
|
||||
- add ADC only implementation for I2C ADC/DAC (#690)
|
||||
|
||||
- **enhancements**
|
||||
- **pixel**
|
||||
- add NewImageFromBytes() function to allow creating image from existing slice
|
||||
- **servo**
|
||||
- Add function `SetAngleWithMicroseconds` (#695)
|
||||
- **onewire**
|
||||
- onewire improvements
|
||||
- **ssd1306**
|
||||
- Add function `SetFlip` and `GetFlip` (#702)
|
||||
- **uc8151**
|
||||
- add FillRectangle() and SetScroll() functions to satisfy tinyterm.Displayer interface
|
||||
- **ssd1306**
|
||||
- add FillRectangle() and SetScroll() functions to satisfy tinyterm.Displayer interface
|
||||
|
||||
- **bugfixes**
|
||||
- **pixel**
|
||||
- fix Monochrome setPixel
|
||||
|
||||
- **docs**
|
||||
- **readme**
|
||||
- discuss need to change variables in examples
|
||||
- **sponsor**
|
||||
- Add sponsor button to key repositories
|
||||
|
||||
|
||||
0.28.0
|
||||
---
|
||||
- **new devices**
|
||||
- **epd2in66b**
|
||||
- Waveshare 2.66inch E-Paper Display Module (B) for Raspberry Pi Pico (#673)
|
||||
- **mcp9808**
|
||||
- Add driver for MCP9808 i2c temperature sensor (#676)
|
||||
|
||||
- **enhancements**
|
||||
- **encoders**
|
||||
- add atsamd21, atsamd51, atsame5x
|
||||
- **pixel**
|
||||
- add support for Monochrome types such as the SSD1306 display
|
||||
- **rtl8720dn**
|
||||
- implement ConnectModeAP
|
||||
- **servo**
|
||||
- add function SetAngle() to simplify API for most common use case
|
||||
- **ssd1306**
|
||||
- add DrawBitmap() function to complete Displayer interface
|
||||
- add rotation functions for Displayer interface
|
||||
- add Sleep() function for Displayer interface
|
||||
- **uc8151**
|
||||
- improvements to speed and also add flicker-free mode based on @antirez code example
|
||||
- update to support all functions needed by tinygl and board package Displayer interface
|
||||
- **wifinina**
|
||||
- implement ConnectModeAP
|
||||
|
||||
- **bugfixes**
|
||||
- **ft6336**
|
||||
- ignore bogus touch events
|
||||
- **pixel**
|
||||
- fix Image[Monochrome].Set for larger images
|
||||
- **uc8151**
|
||||
- correct DrawBitmap() also refactor SendCommand() and SendData() for clarity
|
||||
- **ws2812**
|
||||
- Fix typo and move initialization of neo to init()
|
||||
|
||||
- **examples**
|
||||
- **ws2812**
|
||||
- Simplify examples/ws2812
|
||||
|
||||
|
||||
0.27.0
|
||||
---
|
||||
- **core**
|
||||
- prepare for CGo changes in TinyGo
|
||||
|
||||
- **new devices**
|
||||
- **adafruit4650**
|
||||
- support for Adafruit 4650 feather OLED
|
||||
- **net**
|
||||
- new networking support based on tinygo net package
|
||||
- **pixel**
|
||||
- add package for efficiently working with raw pixel buffers
|
||||
- **rotary**
|
||||
- Adding driver for rotary encoder support
|
||||
- **seesaw**
|
||||
- Adding support for Adafruit Seesaw platform
|
||||
- **sgp30**
|
||||
- add SGP30 air quality sensor
|
||||
- **sk6812**
|
||||
- added support for SK6812 to WS2812 device (#610)
|
||||
|
||||
- **enhancements**
|
||||
- **epd2in13**
|
||||
- add Sleep method like other displays
|
||||
- unify rotation configuration with other displays
|
||||
- use better black/white approximation
|
||||
- **ili9341**
|
||||
- add DrawBitmap method
|
||||
- **lora/lorawan**
|
||||
- LoRa WAN US915 Support
|
||||
- LoRa WAN add setter functions
|
||||
- refactor shared functionality for channels/regions
|
||||
- **mcp2515**
|
||||
- Add more line speeds to mcp2515.go (#626)
|
||||
- **rtl8720dn**
|
||||
- use drivers package version as the driver version
|
||||
- **ssd1306**
|
||||
- improvements needed for Thumby SPI display
|
||||
- **st7735**
|
||||
- make the display generic over RGB565 and RGB444
|
||||
- **st7789**
|
||||
- add DrawBitmap method
|
||||
- make the display generic over RGB565 and RGB444
|
||||
- **wifinina**
|
||||
- add ResetIsHigh cfg switch for MKR 1010 (copied from #561)
|
||||
- maintenence. Also see PR #4085 in the main TinyGo repo
|
||||
- use drivers package version as the driver version
|
||||
|
||||
- **bugfixes**
|
||||
- **adxl345**
|
||||
- Use int16 for ADXL345 readings (#656)
|
||||
- **at24cx**
|
||||
- fixed the description of the device struct
|
||||
- **rtl8720dn**
|
||||
- allow connecting to open wifi access points
|
||||
- fix check for bad Wifi connect
|
||||
- **sh1106**
|
||||
- fix I2C interface and add smoketest
|
||||
- fixed the description of the device struct
|
||||
- **wifinina**
|
||||
- add 'unknown failure' reason code for AP connect
|
||||
- fix concurrency issues with multiple sockets
|
||||
- fix wifinina UDP send
|
||||
|
||||
- **examples**
|
||||
- **ds3231**
|
||||
- fix the description in the example
|
||||
- **lorawan**
|
||||
- add missing functions for simulated interface
|
||||
- modify atcmd and basic demo to support choosing any one of the supported regions at compile time by using ldflags
|
||||
- **net**
|
||||
- all networking examples now using netdev and netlink.
|
||||
|
||||
- **build**
|
||||
- **all**
|
||||
- fix broken testrunner
|
||||
- migrated legacy I2C
|
||||
- add natiu package for tests
|
||||
- **smoketest**
|
||||
- add stack-size param for net tests.
|
||||
- allow stack-size flag as it is needed for net examples
|
||||
|
||||
|
||||
0.26.0
|
||||
---
|
||||
- **core**
|
||||
- i2c iface refactor: Resolve #559
|
||||
- fix uses of legacy i2c WriteRegister calls
|
||||
- add correct Tx implementation for mock I2C interfaces
|
||||
- bump golang.org/x/net version
|
||||
|
||||
- **new devices**
|
||||
- **bma42x**
|
||||
- add new BMA421/BMA425 driver
|
||||
- **ndir**
|
||||
- add Sandbox Electronics NDIR CO2 sensor driver (#580)
|
||||
- **mpu9150**
|
||||
- implement driver for Mpu9150 (#596)
|
||||
- **sht4x**
|
||||
- implement driver for sht4x (#597)
|
||||
- **pcf8523**
|
||||
- implement driver for pcf8523 (#599)
|
||||
|
||||
- **enhancements**
|
||||
- **ssd1306**
|
||||
- improve bus error handling
|
||||
|
||||
- **bugfixes**
|
||||
- **st7789**
|
||||
- fix scrolling when rotated by 180°
|
||||
- **st7789**
|
||||
- fix incorrect Rotation configuration
|
||||
- fix SetScrollArea
|
||||
- **ili9341**
|
||||
- fix SetScrollArea
|
||||
|
||||
- **build**
|
||||
- use latest tag of tinygo-dev container for running tests
|
||||
|
||||
|
||||
0.25.0
|
||||
---
|
||||
|
||||
- **core**
|
||||
- add Sensor interface and Measurement type
|
||||
- **delay**
|
||||
- add new package for cycle-accurate delays
|
||||
|
||||
- **new devices**
|
||||
- **AS560x**
|
||||
- Add support for ams AS560x on-axis magnetic rotary position sensors
|
||||
- **onewire**
|
||||
- first implementation of 1-wire protocol (#505)
|
||||
- **mpu6886**
|
||||
- initial implementation
|
||||
- **ttp229**
|
||||
- initial support for ttp229 (BSF)
|
||||
|
||||
- **enhancements**
|
||||
- **gps**
|
||||
- make the date available in addition to the time (#532)
|
||||
- **i2csoft**
|
||||
- use cycle counting for delays
|
||||
- **ili9341**
|
||||
- add EnableTEOutput to be able to sync drawing with VSYNC
|
||||
- add sleep mode
|
||||
- unify rotation support
|
||||
- **st7735**
|
||||
- add DrawRGBBitmap8 method to draw raw RGB565 buffers
|
||||
- add sleep mode
|
||||
- unify rotation support
|
||||
- **st7789**
|
||||
- added DrawRGBBitmap8 (same as ili9341 & st7735)
|
||||
- allow changing the color format using COLMOD
|
||||
- make it possible to configure gamma
|
||||
- support the chip select pin
|
||||
- update saved rotation in SetRotation
|
||||
- add sleep mode
|
||||
- unify rotation support
|
||||
- **sx126x/sx127x**
|
||||
- Reduce spi buffer size, add missing select when using channels
|
||||
- Remove heap alloc in interrupt, add non blocking channel send/receive, and other cleanups
|
||||
- **wifinina**
|
||||
- add generated strings, improved debugging system and messages
|
||||
- add ResetIsHigh to control the behavior of the RESET pin for boards like the Arduino MKR 1010
|
||||
- only add generated strings when using wifidebug tag
|
||||
|
||||
- **bugfixes**
|
||||
- **ds3231**
|
||||
- Document incorrect leap year 2100
|
||||
- Fix negative temperature conversion
|
||||
- **ili9341**
|
||||
- fix Size() for mirrored rotation
|
||||
- **st7789**
|
||||
- avoid heap allocations after the driver is created
|
||||
- **net**
|
||||
- Revert "(#501) make IP.String() method return something sensible"
|
||||
- **wifinina**
|
||||
- small timing adjustments in Configure() to better ensure device reset
|
||||
|
||||
- **examples**
|
||||
- **sdcard**
|
||||
- remove tinyfs example and replace with link to tinyfs repo in docs
|
||||
- **wifinina**
|
||||
- improve connectToAP() and other needed minor corrections
|
||||
|
||||
- **build**
|
||||
- switch to ghcr.io for docker container
|
||||
- run smoke tests in parallel
|
||||
- **Makefile**
|
||||
- add XTENSA=0 flag to skip Xtensa tests
|
||||
- remove AVR=0 flag
|
||||
|
||||
- **docs**
|
||||
- remove full list of devices from README, better to keep it on the tinygo.org site
|
||||
- update LICENSE year
|
||||
|
||||
|
||||
0.24.0
|
||||
---
|
||||
- **new devices**
|
||||
- **lora**
|
||||
- created shared RadioEvent
|
||||
- move shared config for sx126x/sx127x to single package
|
||||
- **lorawan**
|
||||
- add initial LoRaWAN stack support
|
||||
- Basic implementation of Lorawan Regional Settings and EU868/AU915 regions
|
||||
- **qmi8658c**
|
||||
- Add support for the QMI8658C sensor (#467)
|
||||
- **sh1106**
|
||||
- add support for SH1106 display driver
|
||||
- **sx127x**
|
||||
- Driver for Semtech sx127x radio modules
|
||||
|
||||
- **enhancements**
|
||||
- **bme280**
|
||||
- improve config support
|
||||
- add ReadAltitude() function copied from BMP280 driver
|
||||
- **buzzer**
|
||||
- make all note durations float64
|
||||
- no tone during rest
|
||||
- **dht22**
|
||||
- update DHT22 receive to use runtime/interrupt
|
||||
- **gps**
|
||||
- add support for GLL sentence type, add original sentence to gps errors
|
||||
- improve error handling
|
||||
- improve parsing and add tests to verify
|
||||
- **microbitmatrix**
|
||||
- add link to schema for microbit V2
|
||||
- add smoke test for microbitmatrix with microbit-v2
|
||||
- add support for brightness of led pixels
|
||||
- harmonize v1 and v2 implementation
|
||||
- move Size() to version agnostic part
|
||||
- **mpu6050**
|
||||
- add functions to configure clock, and scaling for accelerometer and gyroscope
|
||||
- **net/http**
|
||||
- add PostForm()
|
||||
- **sx126x**
|
||||
- add Reset() and needed pin
|
||||
- move RadioController into separate file for clarity
|
||||
- pre-define all errors to avoid heap allocations
|
||||
- refactor to RadioController interface to more easily handle non-STM32WL boards and remove duplicated code
|
||||
|
||||
- **vl53l1x**
|
||||
- Add getter for the effective SPAD count
|
||||
- **wifinina**
|
||||
- add support for http server (#480)
|
||||
|
||||
- **bugfixes**
|
||||
- **lsm303agr**
|
||||
- fix I2C address auto increment for multi data read
|
||||
- **net**
|
||||
- (#501) make IP.String() method return something sensible
|
||||
- **mpu6050**
|
||||
- return I2C error when configuring fails
|
||||
- **sx126x**
|
||||
- fix in SetBandwidth function
|
||||
- actually set the frequency when calling SetFrequency()
|
||||
- correct RX/TX pin mapping for TheThingsIndustries GNSE board
|
||||
|
||||
- **examples**
|
||||
- **LoRaWAN**
|
||||
- example with LoRaWAN AT command set implementation
|
||||
- basic example
|
||||
- update all remaining examples for refactored API
|
||||
- **sx126x**
|
||||
- fix bandwidth,tx power in lora//lora_continuous example
|
||||
- **sx127x**
|
||||
- rx/tx example
|
||||
|
||||
- **build**
|
||||
- remove older format build tags
|
||||
- update to actions/checkout@v3
|
||||
- work around for CVE-2022-24765
|
||||
|
||||
|
||||
0.23.0
|
||||
---
|
||||
- **new devices**
|
||||
- Add GC9A01 Driver (#452)
|
||||
- Initial support for VL6180x sensor
|
||||
|
||||
- **enhancements**
|
||||
- **rtl8720dn**
|
||||
- refactor by bringing this driver more in line with wifinina and espat
|
||||
- **ssd1306**
|
||||
- add getter method to the current buffer
|
||||
- **makeybutton**
|
||||
- revise to better match the algorithm defined by the original
|
||||
- **espat,wifinina,rtl8720dn**
|
||||
- change ssid/pass from const to var
|
||||
|
||||
- **bugfixes**
|
||||
- **microbitmatrix**
|
||||
- fix inverted axis
|
||||
- **espat**
|
||||
- Trim quotes from IP returned by espat's GetDNS()
|
||||
- **all**
|
||||
- correct go fmt
|
||||
|
||||
- **examples**
|
||||
- **rtl8720dn**
|
||||
- remove wifi setting
|
||||
- add call to optional debug setting
|
||||
- update all remaining examples for refactored API
|
||||
|
||||
|
||||
0.22.0
|
||||
---
|
||||
- **new devices**
|
||||
- epd: add waveshare 2.9in (v1)
|
||||
- makeybutton: add driver for MakeyMakey-like button
|
||||
|
||||
- **enhancements**
|
||||
- **rtl8720dn**
|
||||
- add UDP close function
|
||||
- improve error handling
|
||||
- **net/http**
|
||||
- improve header parsing
|
||||
- add last-will-and-testament to MQTT
|
||||
- **net/mqtt**
|
||||
- adds keepalive pinging, disconnect, and graceful goroutine cleanup
|
||||
- support for cookies when https
|
||||
- add support for retained messsages
|
||||
|
||||
- **bugfixes**
|
||||
- irremote: Fix irremote reporting incorrect NEC addresses and command codes (#422)
|
||||
- net/http: Fix http.Get() with port specification
|
||||
|
||||
- **build**
|
||||
- Makefile recursively finds unit-tests
|
||||
- switching to GHA
|
||||
|
||||
- **updates**
|
||||
- update tinyfont to v0.3.0
|
||||
- update tinyfs to v0.2.0
|
||||
|
||||
- **examples**
|
||||
- rtl8720dn: add ./examples/rtl8720dn/version
|
||||
|
||||
|
||||
0.21.0
|
||||
---
|
||||
- **new devices**
|
||||
- lsm6ds3tr: initial implementation
|
||||
- UC8151: used in Pimoroni's badger2040 e-paper (#416)
|
||||
- scd4x: implement driver for CO2 sensor
|
||||
|
||||
- **enhancements**
|
||||
- easystepper: Add support for '8-step mode'
|
||||
- vl53l1x: Add functions for setting the device address
|
||||
- sdcard: support thingplus-rp2040
|
||||
- wifinina: add mutex to prevent communication race problems
|
||||
- **ws2812**
|
||||
- support thingplus-rp2040 board
|
||||
- Added 125 MHz rp2040 timing
|
||||
- Added unsafe.Pointer for pointer conversion
|
||||
|
||||
- **bugfixes**
|
||||
- ssd1351: Fix mirrored text on OLED display
|
||||
|
||||
|
||||
0.20.0
|
||||
---
|
||||
- **new devices**
|
||||
- irremote: Add basic infra-red driver
|
||||
- IS31FL3731: add driver for IS31FL3731 matrix LED driver (#370)
|
||||
- l3gd20: add gyro driver
|
||||
- SSD1289: Driver for SSD1289 LCD
|
||||
|
||||
- **enhancements**
|
||||
- **ili9341**
|
||||
- add support for atsame5x
|
||||
- added Feather board support to InitDisplay()
|
||||
- avoid heap allocations
|
||||
- **lps22hb**
|
||||
- pin rename, sync with main repo
|
||||
- **lsmXXX**
|
||||
- unified, error handling, memory management
|
||||
- **max7xx**
|
||||
- Add a SetIntensity() function to max7xx driver and example
|
||||
- **vl53l1x**
|
||||
- Add functions for setting 'region of interest'
|
||||
- Fix switch-case semantics
|
||||
- **ws2812**
|
||||
- add support for m5stamp-c3
|
||||
- convert AVR assembly to C inline assembly
|
||||
- support high-MHz ARMv6M chips like the RP2040
|
||||
- write inline assembly using C instead of Go
|
||||
|
||||
- **bugfixes**
|
||||
- **dht**
|
||||
- fix error check in example
|
||||
- fix humidity and temperature extraction for DHT22 (#358)
|
||||
- **esp8266**
|
||||
- fix ConnectToAccessPoint timeout args
|
||||
- **image**
|
||||
- fix interface
|
||||
- **pca9685**
|
||||
- add buffered one shot write
|
||||
- fix on=0 bug
|
||||
- **wifinina**
|
||||
- correct sendParamStr to handle empty strings, such as when connecting to an unsecured access point
|
||||
|
||||
0.19.0
|
||||
---
|
||||
- **new devices**
|
||||
- ft6336: add support for ft6336
|
||||
- pca9685: PCA9685 driver
|
||||
- shtc3: Sensirion SHTC3 Relative Humidity / Temperature i2c sensor
|
||||
- sx126x: Driver for Semtech sx126x radio modules
|
||||
- xpt2046: XPT2046 Touch driver (#350)
|
||||
- **enhancements**
|
||||
- **hd44780i2c**
|
||||
- clean up for go fmt
|
||||
- Needed fixes and update hd44780i2c.go
|
||||
- **ili9341, ili9342**
|
||||
- add support for m5stack
|
||||
- add support for m5stack-core2
|
||||
- **wifi**
|
||||
- modify to use shared net.Adapter interface for all supported wifi devices
|
||||
- wifinina: remove busy wait
|
||||
- **bugfixes**
|
||||
- **hd44780**
|
||||
- fix 4-bit data length flag
|
||||
- Reset data pins to output mode after reading
|
||||
- Nano 33 BLE drivers (#351)
|
||||
- **docs**
|
||||
- examples/wifi: add unified example for tcpclient that compiles for all supported wifi adaptors
|
||||
|
||||
0.18.0
|
||||
---
|
||||
- **new devices**
|
||||
- apds9960: add support for APDS-9960 Digital Proximity sensor
|
||||
- axp192: add support for AXP192 single Cell Li-Battery and power system management IC
|
||||
- hts221: add support for HTS221 capacitive digital sensor for relative humidity and temperature
|
||||
- i2csoft: add support for software I2C
|
||||
- image: add support for image/jpeg and image/png
|
||||
- lps22hb: add support for LPS22HB MEMS nano pressure sensor
|
||||
- lsm6dox: add support for lsm6dox accelerometer
|
||||
- lsm9ds1: add support for lsm9ds1 accelerometer
|
||||
- **enhancements**
|
||||
- ili9341: change to use drivers.SPI interface
|
||||
- **ws2812**
|
||||
- generate assembly instead of handwriting it
|
||||
- improve timings to be compatible with the WS2811
|
||||
- add support for 168MHz (e.g. Adafruit Feather STM32F405)
|
||||
- add support for RISC-V
|
||||
- wifinina: control nina pins, for example leds
|
||||
- **docs**
|
||||
- rtl8720dn: examples for tcpclient, udpstation, mqtt, and webserver
|
||||
- **wifinina**
|
||||
- nina-fw update docs
|
||||
- examples/wifinina/http-get
|
||||
- ili9341: refactor examples
|
||||
- Fix broken link for SHT3x datasheet
|
||||
- **core**
|
||||
- all: use build directives for both Go1.17 and earlier versions
|
||||
- **bugfixes**
|
||||
- net: fix raddr of tcp conn
|
||||
- mcp3008: fix bitshift bug
|
||||
|
||||
0.17.1
|
||||
---
|
||||
- To correct an error in the release process. Same as 0.17.0.
|
||||
|
||||
0.17.0
|
||||
---
|
||||
- **new devices**
|
||||
- rtl8720dn: add support for rtl8720dn
|
||||
- sdcard: add support for spi sdcard driver, along with fatfs
|
||||
- **enhancements**
|
||||
- apa102: use 4-byte buffer to improve speed
|
||||
- bmi160: avoid heap allocations
|
||||
- ili9341: add standard SPI driver
|
||||
- wifinina
|
||||
- avoid fmt package
|
||||
- Fix RSSI command for WiFiNINA + Print current SSID + Wait for correct time before printing it out + Cleanup
|
||||
- ws2812
|
||||
- rename the pin to ws2812
|
||||
- add tag for nrf52833
|
||||
- Disable interrupts before sending ws2812 data
|
||||
- add support for qtpy and atsame5x
|
||||
- **core**
|
||||
- modules: switch to use tinygo-org version of tinyfs package
|
||||
- all: use machine.Serial as the default output
|
||||
|
||||
0.16.0
|
||||
---
|
||||
- **new devices**
|
||||
- aht20: add device
|
||||
- ina260: add new i2c device
|
||||
- keypad: add 4x4 keypad driver (#226)
|
||||
- max7219: add driver support
|
||||
- mcp2515: add support for mcp2515 CAN device
|
||||
- p1am: support the P1AM-100 hardware watchdog
|
||||
- pcf8563: add support for pcf8563 real time clock
|
||||
- servo: add driver using PWM
|
||||
- tm1637: add support for tm1637 7-segment LED
|
||||
- tone: add package for producing tones using the PWM interface
|
||||
- **enhancements**
|
||||
- pwm: update drivers with PWM to use new interface
|
||||
- wifinina: Make TLS work over WiFiNINA Verified on Arduino Nano33 IoT and nina fw v1.4.5
|
||||
- ssd1306: Enable reset screen for SSD1306 via I2C
|
||||
- st7789: add scrolling functions to match st7735
|
||||
- **bugfixes**
|
||||
- wifinina:
|
||||
- fix getMACAddress and getTime
|
||||
- fix println + cleanup
|
||||
- remove debug flag and remove unnecessary padding call
|
||||
- fix padding and implement missing functions
|
||||
- flash: fix EraseBlocks method which is erasing sectors instead
|
||||
- **core**
|
||||
- all: use interfaces for UART objects
|
||||
- all: do not take the pointer of an I2C object
|
||||
- adc: update drivers with ADC to use new config struct
|
||||
- **testing**
|
||||
- tester:
|
||||
- add a mock for command-oriented i2c devices
|
||||
- add 16-bit register mock device
|
||||
|
||||
- **docs**
|
||||
- ssd1306: example of ssd1306 with 128x64 display over I2C
|
||||
- wifinina:
|
||||
- add information about Adafruit boards with ESP32 wifi coprocessors, and modify examples to remove code that was both not being used, and also prevented many Adafruit boards from being able to be targeted by the examples
|
||||
- update docs to simplify the nina-fw update process
|
||||
- example that connects to AP and prints ip addresses, time and mac
|
||||
- p1am: documentation and example program
|
||||
- add missing new drivers added since last release
|
||||
|
||||
0.15.0
|
||||
---
|
||||
- **new devices**
|
||||
- dht: add DHTXX thermometer
|
||||
- mcp23017: new driver for MCP23017 (I2C port expander)
|
||||
- bmp388: Add bmp388 support (#219)
|
||||
- **enhancements**
|
||||
- hd44780: add a mode to work with boards where the RW pin is grounded
|
||||
- st7789: add scrolling functions to match st7735
|
||||
- microbitmatrix: matrix now working on microbit v2
|
||||
- ds1307: Better interface "ReadTime" instead of "Time"
|
||||
- ws2812: make AVR support more robust
|
||||
- **bugfixes**
|
||||
- all: fix main package in examples
|
||||
- **core**
|
||||
- adc: update all drivers with ADC to use new config struct
|
||||
- spi: remove machine.SPI and replace with drivers.SPI interface for almost all SPI drivers
|
||||
- **testing**
|
||||
- test: run unit tests against i2c drivers and any spi drivers without direct gpio
|
||||
- **docs**
|
||||
- st7789: correct errors on various godoc comments
|
||||
|
||||
0.14.0
|
||||
---
|
||||
- **new devices**
|
||||
|
||||
@@ -8,6 +8,9 @@ We would like your help to make this project better, so we appreciate any contri
|
||||
|
||||
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
|
||||
|
||||
### Driver design
|
||||
Before porting or writing a driver from scratch please read **[Driver Design for TinyGo](https://tinygo.org/docs/guides/driver-design)**.
|
||||
|
||||
### One of the TinyGo drivers is not working as you expect
|
||||
|
||||
Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018-2020 The TinyGo Authors. All rights reserved.
|
||||
Copyright The TinyGo Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
|
||||
@@ -2,160 +2,41 @@
|
||||
clean:
|
||||
@rm -rf build
|
||||
|
||||
FMT_PATHS = ./*.go ./examples/**/*.go
|
||||
FMT_PATHS = ./
|
||||
|
||||
fmt-check:
|
||||
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
||||
|
||||
XTENSA ?= 1
|
||||
smoke-test:
|
||||
@mkdir -p build
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/amg88xx
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/itsybitsy-m0/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/at24cx/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bh1750/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/blinkm/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmi160/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/easystepper/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espconsole/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/esphub/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espstation/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/i2c/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/uart/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/hcsr04/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/customchar/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hd44780/text/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/hd44780i2c/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/hub75/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/basic
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/basic
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/pyportal_boing
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/ili9341/scroll
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/ili9341/scroll
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis3dh/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/lsm303agr/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/lsm6ds3/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mag3110/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp3008/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/microbitmatrix/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mma8653/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mpu6050/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setbuffer/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/pcd8544/setpixel/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/shifter/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/sht3x/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/i2c_128x32/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1306/spi_128x64/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/ssd1331/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7735/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/st7789/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/thermistor/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/fourwire/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/touch/resistive/pyportal_touchpaint/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/vl53l1x/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd2in13x/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=microbit ./examples/waveshare-epd/epd4in2/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/ntpclient/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/udpstation/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/tcpclient/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/wifinina/webclient/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812
|
||||
@md5sum ./build/test.hex
|
||||
ifneq ($(AVR), 0)
|
||||
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
|
||||
@md5sum ./build/test.hex
|
||||
endif
|
||||
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/buzzer/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/veml6070/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l293x/simple/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l293x/speed/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l9110x/simple/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/l9110x/speed/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=nucleo-f103rb ./examples/shiftregister/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=hifive1b ./examples/ssd1351/main.go
|
||||
@md5sum ./build/test.hex
|
||||
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/lis2mdl/main.go
|
||||
@md5sum ./build/test.hex
|
||||
@go run ./smoketest.go -xtensa=$(XTENSA) smoketest.sh
|
||||
|
||||
test: clean fmt-check smoke-test
|
||||
|
||||
# rwildcard is a recursive version of $(wildcard)
|
||||
# https://blog.jgc.org/2011/07/gnu-make-recursive-wildcard-function.html
|
||||
rwildcard=$(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
|
||||
# Recursively find all *_test.go files from cwd & reduce to unique dir names
|
||||
HAS_TESTS = $(sort $(dir $(call rwildcard,,*_test.go)))
|
||||
# Exclude anything we explicitly don't want to test for whatever reason
|
||||
EXCLUDE_TESTS = image waveshare-epd/epd2in66b
|
||||
TESTS = $(filter-out $(addsuffix /%,$(EXCLUDE_TESTS)),$(HAS_TESTS))
|
||||
|
||||
unit-test:
|
||||
@go test -v $(addprefix ./,$(TESTS))
|
||||
|
||||
test: clean fmt-check unit-test smoke-test
|
||||
|
||||
EXCLUDE_DIRS = build cmd examples internal lora ndir netdev netlink tester
|
||||
|
||||
drivers-count:
|
||||
@root_count=$$(find . -mindepth 1 -maxdepth 1 -type d | grep -vE '^\./($(subst $(space),|,$(EXCLUDE_DIRS)))$$' | wc -l); \
|
||||
epd_count=$$(find ./waveshare-epd -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l); \
|
||||
total=$$((root_count + epd_count)); \
|
||||
echo "Total drivers: $$total (root: $$root_count, waveshare-epd: $$epd_count)"
|
||||
|
||||
drivers-list:
|
||||
@{ \
|
||||
find . -mindepth 1 -maxdepth 1 -type d | grep -vE '^\./($(subst $(space),|,$(EXCLUDE_DIRS)))$$'; \
|
||||
if [ -d ./waveshare-epd ]; then find ./waveshare-epd -mindepth 1 -maxdepth 1 -type d; fi; \
|
||||
} | sed 's|^\./||' | sort
|
||||
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
### Table of Contents
|
||||
|
||||
- ["net" Package](#net-package)
|
||||
- [Using "net" Package](#using-net-package)
|
||||
- [Using "net/http" Package](#using-nethttp-package)
|
||||
- [Using "crypto/tls" Package](#using-cryptotls-package)
|
||||
- [Using Sockets](#using-sockets)
|
||||
|
||||
## "net" Package
|
||||
|
||||
TinyGo's "net" package is ported from Go. The port offers a subset of Go's
|
||||
"net" package. The subset maintains Go 1 compatiblity guarantee. A Go
|
||||
application that uses "net" will most-likey just work on TinyGo if the usage is
|
||||
within the subset offered. (There may be external constraints such as limited
|
||||
SRAM on some targets that may limit full "net" functionality).
|
||||
|
||||
Continue below for details on using "net" and "net/http" packages.
|
||||
|
||||
See src/net/READMD.md in the TinyGo repo for more details on maintaining
|
||||
TinyGo's "net" package.
|
||||
|
||||
## Using "net" Package
|
||||
|
||||
Ideally, TinyGo's "net" package would be Go's "net" package and applications
|
||||
using "net" would just work, as-is. TinyGo's net package is a partial port of
|
||||
Go's net package, so some things may not work because they have not been
|
||||
ported.
|
||||
|
||||
There are a few features excluded during the porting process, in particular:
|
||||
|
||||
- No IPv6 support
|
||||
- No DualStack support
|
||||
|
||||
Run ```go doc -all ./src/net``` in TinyGo repo to see full listing of what has
|
||||
been ported. Here is a list of things known to work. You can find examples
|
||||
of these at [examples/net](examples/net/).
|
||||
|
||||
### What is Known to Work
|
||||
|
||||
(These are all IPv4 only).
|
||||
|
||||
- TCP client and server
|
||||
- UDP client
|
||||
- TLS client
|
||||
- HTTP client and server
|
||||
- HTTPS client
|
||||
- NTP client (UDP)
|
||||
- MQTT client (paho & natiu)
|
||||
- WebSocket client and server
|
||||
|
||||
Multiple sockets can be opened in a single app. For example, the app could run
|
||||
as an http server listen on port :80 and also use NTP to get the current time
|
||||
or send something over MQTT. There is a practical limit to the number of
|
||||
active sockets per app, around 8 or 10, so don't go crazy.
|
||||
|
||||
Applications using Go's net package will need a few setup steps to work with
|
||||
TinyGo's net package. The steps are required before using "net".
|
||||
|
||||
### Step 1: Probe to Load Network Driver
|
||||
|
||||
Call Probe() to load the correct network driver for your target. Probe()
|
||||
allows the app to work on multiple targets.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"tinygo.org/x/drivers/netlink/probe"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// load network driver for target
|
||||
link, dev := probe.Probe()
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Probe() will load the driver with default configuration for the target. For
|
||||
custom configuration, the app can open code Probe() for the target
|
||||
requirements.
|
||||
|
||||
Probe() returns a [Netlinker](netlink/README.md) and a
|
||||
[Netdever](netdev/README.md), interfaces implemented by the network driver.
|
||||
Next, we'll use the Netlinker interface to connect the target to an IP network.
|
||||
|
||||
### Step 2: Connect to an IP Network
|
||||
|
||||
Before the net package is fully functional, we need to connect the target to an
|
||||
IP network.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"tinygo.org/x/drivers/netlink"
|
||||
"tinygo.org/x/drivers/netlink/probe"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// load network driver for target
|
||||
link, _ := probe.Probe()
|
||||
|
||||
// Connect target to IP network
|
||||
link.NetConnect(&netlink.ConnectParams{
|
||||
Ssid: "my SSID",
|
||||
Passphrase: "my passphrase",
|
||||
})
|
||||
|
||||
// OK to use "net" from here on
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Optionally, get notified of IP network connects and disconnects:
|
||||
|
||||
```go
|
||||
link.Notify(func(e netlink.Event) {
|
||||
switch e {
|
||||
case netlink.EventNetUp: println("Network UP")
|
||||
case netlink.EventNetDown: println("Network DOWN")
|
||||
})
|
||||
```
|
||||
|
||||
Here is an example of an http server listening on port :8080:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"tinygo.org/x/drivers/netlink"
|
||||
"tinygo.org/x/drivers/netlink/probe"
|
||||
)
|
||||
|
||||
func HelloServer(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// load network driver for target
|
||||
link, _ := probe.Probe()
|
||||
|
||||
// Connect target to IP network
|
||||
link.NetConnect(&netlink.ConnectParams{
|
||||
Ssid: "my SSID",
|
||||
Passphrase: "my passphrase",
|
||||
})
|
||||
|
||||
// Serve it up
|
||||
http.HandleFunc("/", HelloServer)
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
```
|
||||
|
||||
## Using "net/http" Package
|
||||
|
||||
TinyGo's net/http package is a partial port of Go's net/http package, providing
|
||||
a subset of the full net/http package. There are a few features excluded
|
||||
during the porting process, in particular:
|
||||
|
||||
- No HTTP/2 support
|
||||
- No TLS support for HTTP servers (no https servers)
|
||||
- HTTP client request can't be reused
|
||||
|
||||
HTTP client methods (http.Get, http.Head, http.Post, and http.PostForm) are
|
||||
functional. Dial clients support both HTTP and HTTPS URLs.
|
||||
|
||||
HTTP server methods and objects are mostly ported, but for HTTP only; HTTPS
|
||||
servers are not supported.
|
||||
|
||||
HTTP request and response handling code is mostly ported, so most the intricacy
|
||||
of parsing and writing headers is handled as in the full net/http package.
|
||||
|
||||
Run ```go doc -all ./src/net/http``` in TinyGo repo to see full listing.
|
||||
|
||||
## Using "crypto/tls" Package
|
||||
|
||||
TinyGo's TLS support (crypto/tls) relies on hardware offload of the TLS
|
||||
protocol. This is different from Go's crypto/tls package which handles the TLS
|
||||
protocol in software.
|
||||
|
||||
TinyGo's TLS support is only available for client applications. You can
|
||||
http.Get() to an https:// address, but you cannot http.ListenAndServeTLS() an
|
||||
https server.
|
||||
|
||||
The offloading hardware has pre-defined TLS certificates built-in.
|
||||
|
||||
## Using Sockets
|
||||
|
||||
The Netdever interface is a BSD socket-like interface so an application can make direct
|
||||
socket calls, bypassing the "net" package for the lowest overhead.
|
||||
|
||||
Here is a simple TCP client application using direct sockets:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net" // only need to parse IP address
|
||||
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
"tinygo.org/x/drivers/netlink"
|
||||
"tinygo.org/x/drivers/netlink/probe"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// load network driver for target
|
||||
link, dev := probe.Probe()
|
||||
|
||||
// Connect target to IP network
|
||||
link.NetConnect(&netlink.ConnectParams{
|
||||
Ssid: "my SSID",
|
||||
Passphrase: "my passphrase",
|
||||
})
|
||||
|
||||
// omit error handling
|
||||
|
||||
sock, _ := dev.Socket(netdev.AF_INET, netdev.SOCK_STREAM, netdev.IPPROTO_TCP)
|
||||
|
||||
dev.Connect(sock, "", net.ParseIP("10.0.0.100"), 8080)
|
||||
dev.Send(sock, []bytes("hello"), 0, 0)
|
||||
|
||||
dev.Close(sock)
|
||||
link.NetDisconnect()
|
||||
}
|
||||
```
|
||||
@@ -1,9 +1,15 @@
|
||||
# TinyGo Drivers
|
||||
|
||||
[](https://pkg.go.dev/tinygo.org/x/drivers) [](https://circleci.com/gh/tinygo-org/drivers/tree/dev)
|
||||
[](https://pkg.go.dev/tinygo.org/x/drivers) [](https://github.com/tinygo-org/drivers/actions/workflows/build.yml)
|
||||
|
||||
|
||||
This package provides a collection of hardware drivers for devices that can be used together with [TinyGo](https://tinygo.org).
|
||||
This package provides a collection of over 140 different hardware drivers for devices such as sensors, displays, wireless adaptors, and actuators, that can be used together with [TinyGo](https://tinygo.org).
|
||||
|
||||
For the complete list, please see:
|
||||
https://tinygo.org/docs/reference/devices/
|
||||
|
||||
> [!IMPORTANT]
|
||||
> You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you!
|
||||
|
||||
## Installing
|
||||
|
||||
@@ -13,7 +19,7 @@ go get tinygo.org/x/drivers
|
||||
|
||||
## How to use
|
||||
|
||||
Here is an example in TinyGo that uses the BMP180 digital barometer:
|
||||
Here is an example in TinyGo that uses the BMP180 digital barometer. This example should work on any board that supports I2C:
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -50,65 +56,27 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Currently supported devices
|
||||
## Examples Using GPIO or SPI
|
||||
|
||||
The following 53 devices are supported.
|
||||
If compiling these examples directly you are likely to need to make minor changes to the defined variables to map the pins for the board you are using. For example, this block in main.go:
|
||||
|
||||
| Device Name | Interface Type |
|
||||
|----------|-------------|
|
||||
| [ADT7410 I2C Temperature Sensor](https://www.analog.com/media/en/technical-documentation/data-sheets/ADT7410.pdf) | I2C |
|
||||
| [ADXL345 accelerometer](http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf) | I2C |
|
||||
| [AMG88xx 8x8 Thermal camera sensor](https://cdn-learn.adafruit.com/assets/assets/000/043/261/original/Grid-EYE_SPECIFICATIONS%28Reference%29.pdf) | I2C |
|
||||
| [APA102 RGB LED](https://cdn-shop.adafruit.com/product-files/2343/APA102C.pdf) | SPI |
|
||||
| [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C |
|
||||
| [BBC micro:bit LED matrix](https://github.com/bbcmicrobit/hardware/blob/master/SCH_BBC-Microbit_V1.3B.pdf) | GPIO |
|
||||
| [BH1750 ambient light sensor](https://www.mouser.com/ds/2/348/bh1750fvi-e-186247.pdf) | I2C |
|
||||
| [BlinkM RGB LED](http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf) | I2C |
|
||||
| [BME280 humidity/pressure sensor](https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf) | I2C |
|
||||
| [BMI160 accelerometer/gyroscope](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmi160-ds000.pdf) | SPI |
|
||||
| [BMP180 barometer](https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf) | I2C |
|
||||
| [BMP280 temperature/barometer](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf) | I2C |
|
||||
| [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO |
|
||||
| [DS1307 real time clock](https://datasheets.maximintegrated.com/en/ds/DS1307.pdf) | I2C |
|
||||
| [DS3231 real time clock](https://datasheets.maximintegrated.com/en/ds/DS3231.pdf) | I2C |
|
||||
| [ESP32 as WiFi Coprocessor with Arduino nina-fw](https://github.com/arduino/nina-fw) | SPI |
|
||||
| [ESP8266/ESP32 AT Command set for WiFi/TCP/UDP](https://github.com/espressif/esp32-at) | UART |
|
||||
| [GPS module](https://www.u-blox.com/en/product/neo-6-series) | I2C/UART |
|
||||
| [HC-SR04 Ultrasonic distance sensor](https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf) | GPIO |
|
||||
| [HD44780 LCD controller](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) | GPIO/I2C |
|
||||
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
|
||||
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
|
||||
| [L293x motor driver](https://www.ti.com/lit/ds/symlink/l293d.pdf) | GPIO/PWM |
|
||||
| [L9110x motor driver](https://www.elecrow.com/download/datasheet-l9110.pdf) | GPIO/PWM |
|
||||
| [LIS2MDL magnetometer](https://www.st.com/resource/en/datasheet/lis2mdl.pdf) | I2C |
|
||||
| [LIS3DH accelerometer](https://www.st.com/resource/en/datasheet/lis3dh.pdf) | I2C |
|
||||
| [LSM6DS3 accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3.pdf) | I2C |
|
||||
| [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C |
|
||||
| [MCP3008 analog to digital converter (ADC)](http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf) | SPI |
|
||||
| [Microphone - PDM](https://cdn-learn.adafruit.com/assets/assets/000/049/977/original/MP34DT01-M.pdf) | I2S/PDM |
|
||||
| [MMA8653 accelerometer](https://www.nxp.com/docs/en/data-sheet/MMA8653FC.pdf) | I2C |
|
||||
| [MPU6050 accelerometer/gyroscope](https://store.invensense.com/datasheets/invensense/MPU-6050_DataSheet_V3%204.pdf) | I2C |
|
||||
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
|
||||
| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO |
|
||||
| [Semihosting](https://wiki.segger.com/Semihosting) | Debug |
|
||||
| [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO |
|
||||
| [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO |
|
||||
| [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
|
||||
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
|
||||
| [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI |
|
||||
| [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI |
|
||||
| [SSD1351 OLED display](https://download.mikroe.com/documents/datasheets/ssd1351-revision-1.3.pdf) | SPI |
|
||||
| [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | SPI |
|
||||
| [ST7789 TFT color display](https://cdn-shop.adafruit.com/product-files/3787/3787_tft_QT154H2201__________20190228182902.pdf) | SPI |
|
||||
| [Stepper motor "Easystepper" controller](https://en.wikipedia.org/wiki/Stepper_motor) | GPIO |
|
||||
| [Thermistor](https://www.farnell.com/datasheets/33552.pdf) | ADC |
|
||||
| [TMP102 I2C Temperature Sensor](https://download.mikroe.com/documents/datasheets/tmp102-data-sheet.pdf) | I2C |
|
||||
| [VEML6070 UV light sensor](https://www.vishay.com/docs/84277/veml6070.pdf) | I2C |
|
||||
| [VL53L1X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl53l1x.pdf) | I2C |
|
||||
| [Waveshare 2.13" (B & C) e-paper display](https://www.waveshare.com/w/upload/d/d3/2.13inch-e-paper-b-Specification.pdf) | SPI |
|
||||
| [Waveshare 2.13" e-paper display](https://www.waveshare.com/w/upload/e/e6/2.13inch_e-Paper_Datasheet.pdf) | SPI |
|
||||
| [Waveshare 4.2" e-paper B/W display](https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf) | SPI |
|
||||
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
|
||||
```golang
|
||||
var (
|
||||
spi = machine.SPI0
|
||||
csPin = machine.D5
|
||||
)
|
||||
```
|
||||
|
||||
It might not be obvious, but you need to change these to match how you wired your specific board. Constants are [defined for each supported microcontroller](https://tinygo.org/docs/reference/microcontrollers/).
|
||||
|
||||
For example, to change the definitions for use on a Raspberry Pi Pico using typical wiring, you might need to do this:
|
||||
|
||||
```golang
|
||||
var (
|
||||
spi = machine.SPI0
|
||||
csPin = machine.GP17
|
||||
)
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// Package adafruit4650 implements a driver for the Adafruit FeatherWing OLED - 128x64 OLED display.
|
||||
// The display is backed itself by a SH1107 driver chip.
|
||||
//
|
||||
// Store: https://www.adafruit.com/product/4650
|
||||
//
|
||||
// Documentation: https://learn.adafruit.com/adafruit-128x64-oled-featherwing
|
||||
package adafruit4650
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
const DefaultAddress = 0x3c
|
||||
|
||||
const (
|
||||
commandSetLowColumn = 0x00
|
||||
commandSetHighColumn = 0x10
|
||||
commandSetPage = 0xb0
|
||||
)
|
||||
|
||||
const (
|
||||
width = 128
|
||||
height = 64
|
||||
)
|
||||
|
||||
// Device represents an Adafruit 4650 device
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint8
|
||||
buffer []byte
|
||||
width int16
|
||||
height int16
|
||||
}
|
||||
|
||||
// New creates a new device, not configuring anything yet.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: DefaultAddress,
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure initializes the display with default configuration
|
||||
func (d *Device) Configure() error {
|
||||
|
||||
bufferSize := d.width * d.height / 8
|
||||
d.buffer = make([]byte, bufferSize)
|
||||
|
||||
// This sequence is an amalgamation of the datasheet, official Arduino driver, CircuitPython driver and other drivers
|
||||
initSequence := []byte{
|
||||
0xae, // display off, sleep mode
|
||||
//0xd5, 0x41, // set display clock divider (from original datasheet)
|
||||
0xd5, 0x51, // set display clock divider (from Adafruit driver)
|
||||
0xd9, 0x22, // pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
|
||||
0x20, // memory mode
|
||||
0x81, 0x4f, // contrast setting = 0x4f
|
||||
0xad, 0x8a, // set dc/dc pump
|
||||
0xa0, // segment remap, flip-x
|
||||
0xc0, // common output scan direction
|
||||
0xdc, 0x00, // set display start line 0 (POR=0)
|
||||
0xa8, 0x3f, // multiplex ratio, height - 1 = 0x3f
|
||||
0xd3, 0x60, // set display offset mode = 0x60
|
||||
0xdb, 0x35, // VCOM deselect level = 0.770 (POR)
|
||||
0xa4, // entire display off, retain RAM, normal status (POR)
|
||||
0xa6, // normal (not reversed) display
|
||||
0xaf, // display on
|
||||
}
|
||||
|
||||
err := d.writeCommands(initSequence)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// recommended in the datasheet, same in other drivers
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearDisplay clears the image buffer as well as the actual display
|
||||
func (d *Device) ClearDisplay() error {
|
||||
d.ClearBuffer()
|
||||
return d.Display()
|
||||
}
|
||||
|
||||
// ClearBuffer clears the buffer
|
||||
func (d *Device) ClearBuffer() {
|
||||
bzero(d.buffer)
|
||||
}
|
||||
|
||||
// SetPixel modifies the internal buffer. Since this display has a bit-depth of 1 bit any non-zero
|
||||
// color component will be treated as 'on', otherwise 'off'.
|
||||
func (d *Device) SetPixel(x int16, y int16, c color.RGBA) {
|
||||
if x < 0 || x >= d.width || y < 0 || y >= d.height {
|
||||
return
|
||||
}
|
||||
|
||||
// RAM layout
|
||||
// *-----> y
|
||||
// |
|
||||
// x| col0 col1 ... col63
|
||||
// v p0 a0 b0 ..
|
||||
// a1 b1 ..
|
||||
// .. .. ..
|
||||
// a7 b7 ..
|
||||
// p1 a0 b0
|
||||
// a1 b1
|
||||
//
|
||||
|
||||
//flip y - so the display orientation matches the silk screen labeling etc.
|
||||
y = d.height - y - 1
|
||||
|
||||
page := x / 8
|
||||
bytesPerPage := d.height
|
||||
byteIndex := y + bytesPerPage*page
|
||||
bit := x % 8
|
||||
if (c.R | c.G | c.B) != 0 {
|
||||
d.buffer[byteIndex] |= 1 << uint8(bit)
|
||||
} else {
|
||||
d.buffer[byteIndex] &^= 1 << uint8(bit)
|
||||
}
|
||||
}
|
||||
|
||||
// Display sends the whole buffer to the screen
|
||||
func (d *Device) Display() error {
|
||||
|
||||
bytesPerPage := d.height
|
||||
|
||||
pages := (d.width + 7) / 8
|
||||
for page := int16(0); page < pages; page++ {
|
||||
|
||||
err := d.setRAMPosition(uint8(page), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offset := page * bytesPerPage
|
||||
err = d.writeRAM(d.buffer[offset : offset+bytesPerPage])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setRAMPosition updates the device's current page and column position
|
||||
func (d *Device) setRAMPosition(page uint8, column uint8) error {
|
||||
if page > 15 {
|
||||
panic("page out of bounds")
|
||||
}
|
||||
if column > 127 {
|
||||
panic("column out of bounds")
|
||||
}
|
||||
setPage := commandSetPage | (page & 0xF)
|
||||
|
||||
lo := column & 0xF
|
||||
setLowColumn := commandSetLowColumn | lo
|
||||
|
||||
hi := (column >> 4) & 0x7
|
||||
setHighColumn := commandSetHighColumn | hi
|
||||
|
||||
cmds := []byte{
|
||||
setPage,
|
||||
setLowColumn,
|
||||
setHighColumn,
|
||||
}
|
||||
|
||||
return d.writeCommands(cmds)
|
||||
}
|
||||
|
||||
// Size returns the current size of the display.
|
||||
func (d *Device) Size() (w, h int16) {
|
||||
return d.width, d.height
|
||||
}
|
||||
|
||||
func (d *Device) writeCommands(commands []byte) error {
|
||||
onlyCommandsFollowing := byte(0x00)
|
||||
return d.bus.Tx(uint16(d.Address), append([]byte{onlyCommandsFollowing}, commands...), nil)
|
||||
}
|
||||
|
||||
func (d *Device) writeRAM(data []byte) error {
|
||||
onlyRAMFollowing := byte(0x40)
|
||||
return d.bus.Tx(uint16(d.Address), append([]byte{onlyRAMFollowing}, data...), nil)
|
||||
}
|
||||
|
||||
func bzero(buf []byte) {
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package adafruit4650
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/tinyfont"
|
||||
"tinygo.org/x/tinyfont/freemono"
|
||||
)
|
||||
|
||||
//go:embed expected_hello_world.png
|
||||
var expectedHelloWorld []byte
|
||||
|
||||
// mockBus mocks a fake i2c device adafruit4650 display.
|
||||
// The memory layout assumes that clients set up the device in a particular way and always send complete
|
||||
// pages to the device buffer.
|
||||
type mockBus struct {
|
||||
img draw.Image
|
||||
line int
|
||||
addr uint8
|
||||
currentPage int
|
||||
currentColumn int
|
||||
}
|
||||
|
||||
func (m *mockBus) Tx(addr uint16, w, r []byte) error {
|
||||
if addr != uint16(m.addr) {
|
||||
panic("unexpected address")
|
||||
}
|
||||
if r != nil {
|
||||
panic("mock does not support reads")
|
||||
}
|
||||
|
||||
if w[0] == 0x00 {
|
||||
if w[1]&0xf0 == 0xb0 {
|
||||
m.currentPage = int(w[1] & 0x0f)
|
||||
|
||||
lo := w[2] & 0x0f
|
||||
hi := w[2] & 0x07
|
||||
m.currentColumn = int(hi<<4 | lo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if w[0] != 0x40 {
|
||||
panic("unexpected first byte: " + hex.EncodeToString(w[0:1]))
|
||||
}
|
||||
|
||||
return m.writeRAM(w[1:])
|
||||
}
|
||||
|
||||
func newMock() *mockBus {
|
||||
|
||||
m := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
return &mockBus{img: m, addr: DefaultAddress, currentPage: -1, currentColumn: -1}
|
||||
}
|
||||
|
||||
func (m *mockBus) writeRAM(data []byte) error {
|
||||
|
||||
// RAM layout
|
||||
// *-----> y
|
||||
// |
|
||||
// x| col0 col1 ... col63
|
||||
// v p0 a0 b0 ..
|
||||
// a1 b1 ..
|
||||
// .. .. ..
|
||||
// a7 b7 ..
|
||||
// p1 a0 b0
|
||||
// a1 b1
|
||||
//
|
||||
|
||||
fmt.Printf("writing page %d\n", m.currentPage)
|
||||
// assuming entire pages will be written
|
||||
for x := 0; x < 8; x++ {
|
||||
for y := 0; y < height; y++ {
|
||||
|
||||
col := data[y]
|
||||
|
||||
c := color.Black
|
||||
if col&(1<<x) != 0 {
|
||||
c = color.White
|
||||
}
|
||||
|
||||
m.img.Set(x+m.currentPage*8, height-y-1, c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockBus) toImage() *image.RGBA {
|
||||
|
||||
container := image.NewRGBA(m.img.Bounds().Inset(-1))
|
||||
draw.Draw(container, container.Bounds(), image.NewUniform(color.RGBA{G: 255, A: 255}), image.Point{}, draw.Over)
|
||||
draw.Draw(container, m.img.Bounds(), m.img, image.Point{}, draw.Over)
|
||||
return container
|
||||
}
|
||||
|
||||
func TestDevice_Display(t *testing.T) {
|
||||
|
||||
bus := newMock()
|
||||
dev := New(bus)
|
||||
|
||||
dev.Configure()
|
||||
|
||||
drawPlus(&dev)
|
||||
drawHellowWorld(&dev)
|
||||
|
||||
//when
|
||||
dev.Display()
|
||||
|
||||
//then
|
||||
actual := bus.toImage()
|
||||
|
||||
expected, err := png.Decode(bytes.NewReader(expectedHelloWorld))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assertEqualImages(t, actual, expected)
|
||||
}
|
||||
|
||||
func drawPlus(d drivers.Displayer) {
|
||||
for i := int16(0); i < 128; i++ {
|
||||
d.SetPixel(i, 32, color.RGBA{R: 1})
|
||||
}
|
||||
for i := int16(0); i < 64; i++ {
|
||||
d.SetPixel(64, i, color.RGBA{R: 1})
|
||||
}
|
||||
}
|
||||
|
||||
func drawHellowWorld(d drivers.Displayer) {
|
||||
tinyfont.WriteLine(d, &freemono.Regular9pt7b, 0, 32, "Hello World!", color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
||||
}
|
||||
|
||||
func assertEqualImages(t testing.TB, actual, expected image.Image) {
|
||||
|
||||
if actual.Bounds().Dx() != expected.Bounds().Dx() || actual.Bounds().Dy() != expected.Bounds().Dy() {
|
||||
f := writeImage(actual)
|
||||
t.Fatalf("differing size: was %v, expected %v, saved actual to %s", actual.Bounds(), expected.Bounds(), f)
|
||||
}
|
||||
|
||||
bb := expected.Bounds()
|
||||
for x := bb.Min.X; x < bb.Max.X; x++ {
|
||||
for y := bb.Min.Y; y < bb.Max.Y; y++ {
|
||||
actualBB := actual.Bounds()
|
||||
if actual.At(x+actualBB.Min.X, y+actualBB.Min.Y) != expected.At(x, y) {
|
||||
f := writeImage(actual)
|
||||
t.Fatalf("different pixel at %d/%d: %v != %v, saved actual at %s", x, y, actual.At(x, y), expected.At(x, y), f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeImage(img image.Image) string {
|
||||
|
||||
fn := fmt.Sprintf("%d.png", time.Now().Unix())
|
||||
f, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = png.Encode(f, img)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 449 B |
+4
-4
@@ -1,13 +1,13 @@
|
||||
// Package adt7410 provides a driver for the adt7410 I2C Temperature Sensor.
|
||||
//
|
||||
// Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADT7410.pdf
|
||||
//
|
||||
package adt7410 // import "tinygo.org/x/drivers/adt7410"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
type Error uint8
|
||||
@@ -55,7 +55,7 @@ func (d *Device) Configure() (err error) {
|
||||
// Connected returns whether sensor has been found.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
d.bus.ReadRegister(uint8(d.Address), RegID, data)
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), RegID, data)
|
||||
return data[0]&0xF8 == 0xC8
|
||||
}
|
||||
|
||||
@@ -82,11 +82,11 @@ func (d *Device) writeByte(reg uint8, data byte) {
|
||||
}
|
||||
|
||||
func (d *Device) readByte(reg uint8) byte {
|
||||
d.bus.ReadRegister(d.Address, reg, d.buf)
|
||||
legacy.ReadRegister(d.bus, d.Address, reg, d.buf)
|
||||
return d.buf[0]
|
||||
}
|
||||
|
||||
func (d *Device) readUint16(reg uint8) uint16 {
|
||||
d.bus.ReadRegister(d.Address, reg, d.buf)
|
||||
legacy.ReadRegister(d.bus, d.Address, reg, d.buf)
|
||||
return uint16(d.buf[0])<<8 | uint16(d.buf[1])
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ func TestWhoAmI(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fake := tester.NewI2CDevice(c, Address)
|
||||
fake.SetupRegisters(defaultRegisters())
|
||||
copy(fake.Registers[:], defaultRegisters())
|
||||
bus.AddDevice(fake)
|
||||
|
||||
dev := New(bus)
|
||||
c.Assert(dev.Connected(), qt.Equals, true)
|
||||
|
||||
fake.SetupRegister(RegID, 0x99)
|
||||
fake.Registers[RegID] = 0x99
|
||||
c.Assert(dev.Connected(), qt.Equals, false)
|
||||
}
|
||||
|
||||
|
||||
+20
-18
@@ -3,10 +3,12 @@
|
||||
// Datasheet EN: http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf
|
||||
//
|
||||
// Datasheet JP: http://www.analog.com/media/jp/technical-documentation/data-sheets/ADXL345_jp.pdf
|
||||
//
|
||||
package adxl345 // import "tinygo.org/x/drivers/adxl345"
|
||||
|
||||
import "tinygo.org/x/drivers"
|
||||
import (
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
type Range uint8
|
||||
type Rate uint8
|
||||
@@ -69,21 +71,21 @@ func New(bus drivers.I2C) Device {
|
||||
|
||||
// Configure sets up the device for communication
|
||||
func (d *Device) Configure() {
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_DATA_FORMAT, []byte{d.dataFormat.toByte()})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_DATA_FORMAT, []byte{d.dataFormat.toByte()})
|
||||
}
|
||||
|
||||
// Halt stops the sensor, values will not updated
|
||||
func (d *Device) Halt() {
|
||||
d.powerCtl.measure = 0
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
|
||||
}
|
||||
|
||||
// Restart makes reading the sensor working again after a halt
|
||||
func (d *Device) Restart() {
|
||||
d.powerCtl.measure = 1
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_POWER_CTL, []byte{d.powerCtl.toByte()})
|
||||
}
|
||||
|
||||
// ReadAcceleration reads the current acceleration from the device and returns
|
||||
@@ -93,18 +95,18 @@ func (d *Device) Restart() {
|
||||
func (d *Device) ReadAcceleration() (x int32, y int32, z int32, err error) {
|
||||
rx, ry, rz := d.ReadRawAcceleration()
|
||||
|
||||
x = d.dataFormat.convertToIS(rx)
|
||||
y = d.dataFormat.convertToIS(ry)
|
||||
z = d.dataFormat.convertToIS(rz)
|
||||
x = int32(d.dataFormat.convertToIS(rx))
|
||||
y = int32(d.dataFormat.convertToIS(ry))
|
||||
z = int32(d.dataFormat.convertToIS(rz))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadRawAcceleration reads the sensor values and returns the raw x, y and z axis
|
||||
// from the adxl345.
|
||||
func (d *Device) ReadRawAcceleration() (x int32, y int32, z int32) {
|
||||
func (d *Device) ReadRawAcceleration() (x int16, y int16, z int16) {
|
||||
data := []byte{0, 0, 0, 0, 0, 0}
|
||||
d.bus.ReadRegister(uint8(d.Address), REG_DATAX0, data)
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), REG_DATAX0, data)
|
||||
|
||||
x = readIntLE(data[0], data[1])
|
||||
y = readIntLE(data[2], data[3])
|
||||
@@ -120,25 +122,25 @@ func (d *Device) UseLowPower(power bool) {
|
||||
} else {
|
||||
d.bwRate.lowPower = 0
|
||||
}
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
|
||||
}
|
||||
|
||||
// SetRate change the current rate of the sensor
|
||||
func (d *Device) SetRate(rate Rate) bool {
|
||||
d.bwRate.rate = rate & 0x0F
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_BW_RATE, []byte{d.bwRate.toByte()})
|
||||
return true
|
||||
}
|
||||
|
||||
// SetRange change the current range of the sensor
|
||||
func (d *Device) SetRange(sensorRange Range) bool {
|
||||
d.dataFormat.sensorRange = sensorRange & 0x03
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_DATA_FORMAT, []byte{d.dataFormat.toByte()})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_DATA_FORMAT, []byte{d.dataFormat.toByte()})
|
||||
return true
|
||||
}
|
||||
|
||||
// convertToIS adjusts the raw values from the adxl345 with the range configuration
|
||||
func (d *dataFormat) convertToIS(rawValue int32) int32 {
|
||||
func (d *dataFormat) convertToIS(rawValue int16) int16 {
|
||||
switch d.sensorRange {
|
||||
case RANGE_2G:
|
||||
return rawValue * 4 // rawValue * 2 * 1000 / 512
|
||||
@@ -188,6 +190,6 @@ func (b *bwRate) toByte() (bits uint8) {
|
||||
}
|
||||
|
||||
// readInt converts two bytes to int16
|
||||
func readIntLE(msb byte, lsb byte) int32 {
|
||||
return int32(uint16(msb) | uint16(lsb)<<8)
|
||||
func readIntLE(msb byte, lsb byte) int16 {
|
||||
return int16(uint16(msb) | uint16(lsb)<<8)
|
||||
}
|
||||
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package aht20
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to an AHT20 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
humidity uint32
|
||||
temp uint32
|
||||
}
|
||||
|
||||
// New creates a new AHT20 connection. The I2C bus must already be
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure the device
|
||||
func (d *Device) Configure() {
|
||||
// Check initialization state
|
||||
status := d.Status()
|
||||
if status&STATUS_CALIBRATED == 1 {
|
||||
// Device is initialized
|
||||
return
|
||||
}
|
||||
|
||||
// Force initialization
|
||||
d.bus.Tx(d.Address, []byte{CMD_INITIALIZE, 0x08, 0x00}, nil)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Reset the device
|
||||
func (d *Device) Reset() {
|
||||
d.bus.Tx(d.Address, []byte{CMD_SOFTRESET}, nil)
|
||||
}
|
||||
|
||||
// Status of the device
|
||||
func (d *Device) Status() byte {
|
||||
data := []byte{0}
|
||||
|
||||
d.bus.Tx(d.Address, []byte{CMD_STATUS}, data)
|
||||
|
||||
return data[0]
|
||||
}
|
||||
|
||||
// Read the temperature and humidity
|
||||
//
|
||||
// The actual temperature and humidity are stored
|
||||
// and can be accessed using `Temp` and `Humidity`.
|
||||
func (d *Device) Read() error {
|
||||
d.bus.Tx(d.Address, []byte{CMD_TRIGGER, 0x33, 0x00}, nil)
|
||||
|
||||
data := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
for retry := 0; retry < 3; retry++ {
|
||||
time.Sleep(80 * time.Millisecond)
|
||||
err := d.bus.Tx(d.Address, nil, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If measurement complete, store values
|
||||
if data[0]&STATUS_CALIBRATED != 0 && data[0]&STATUS_BUSY == 0 {
|
||||
d.humidity = uint32(data[1])<<12 | uint32(data[2])<<4 | uint32(data[3])>>4
|
||||
d.temp = (uint32(data[3])&0xF)<<16 | uint32(data[4])<<8 | uint32(data[5])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ErrTimeout
|
||||
}
|
||||
|
||||
func (d *Device) RawHumidity() uint32 {
|
||||
return d.humidity
|
||||
}
|
||||
|
||||
func (d *Device) RawTemp() uint32 {
|
||||
return d.temp
|
||||
}
|
||||
|
||||
func (d *Device) RelHumidity() float32 {
|
||||
return (float32(d.humidity) * 100) / 0x100000
|
||||
}
|
||||
|
||||
func (d *Device) DeciRelHumidity() int32 {
|
||||
return (int32(d.humidity) * 1000) / 0x100000
|
||||
}
|
||||
|
||||
// Temperature in degrees celsius
|
||||
func (d *Device) Celsius() float32 {
|
||||
return (float32(d.temp*200.0) / 0x100000) - 50
|
||||
}
|
||||
|
||||
// Temperature in mutiples of one tenth of a degree celsius
|
||||
//
|
||||
// Using this method avoids floating point calculations.
|
||||
func (d *Device) DeciCelsius() int32 {
|
||||
return ((int32(d.temp) * 2000) / 0x100000) - 500
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package aht20
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"tinygo.org/x/drivers/tester"
|
||||
)
|
||||
|
||||
func TestDefaultI2CAddress(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
dev := New(bus)
|
||||
c.Assert(uint8(dev.Address), qt.Equals, uint8(Address))
|
||||
}
|
||||
|
||||
func TestInitialization(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := tester.NewI2CDeviceCmd(c, Address)
|
||||
fdev.Commands = defaultCommands()
|
||||
bus.AddDevice(fdev)
|
||||
|
||||
// Set status to uninitialized to force initialization
|
||||
fdev.Commands[CMD_STATUS].Response[0] = 0x0C
|
||||
|
||||
dev := New(bus)
|
||||
dev.Configure()
|
||||
|
||||
// Check initialization command invoked
|
||||
c.Assert(fdev.Commands[CMD_INITIALIZE].Invocations > 0, qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestRead(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
bus := tester.NewI2CBus(c)
|
||||
fdev := tester.NewI2CDeviceCmd(c, Address)
|
||||
fdev.Commands = defaultCommands()
|
||||
bus.AddDevice(fdev)
|
||||
|
||||
dev := New(bus)
|
||||
dev.Read()
|
||||
|
||||
// Should be 25deg (250 decidegrees)
|
||||
c.Assert(dev.DeciCelsius(), qt.Equals, int32(250))
|
||||
|
||||
// Should be 36.3% (363 decipercent)
|
||||
c.Assert(dev.DeciRelHumidity(), qt.Equals, int32(363))
|
||||
}
|
||||
|
||||
func defaultCommands() map[uint8]*tester.Cmd {
|
||||
return map[uint8]*tester.Cmd{
|
||||
CMD_INITIALIZE: {
|
||||
Command: []byte{0xBE},
|
||||
Mask: []byte{0xFF},
|
||||
Response: []byte{},
|
||||
},
|
||||
CMD_TRIGGER: {
|
||||
Command: []byte{0xAC, 0x33, 0x00},
|
||||
Mask: []byte{0xFF, 0xFF, 0xFF},
|
||||
Response: []byte{0x1C, 0x5D, 0x10, 0x66, 0x01, 0xD2, 0x93},
|
||||
},
|
||||
CMD_SOFTRESET: {
|
||||
Command: []byte{0xBA},
|
||||
Mask: []byte{0xFF},
|
||||
Response: []byte{},
|
||||
},
|
||||
CMD_STATUS: {
|
||||
Command: []byte{0x71},
|
||||
Mask: []byte{0xFF},
|
||||
Response: []byte{0x1C},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package aht20
|
||||
|
||||
import "errors"
|
||||
|
||||
const (
|
||||
Address = 0x38
|
||||
|
||||
CMD_INITIALIZE = 0xBE
|
||||
CMD_STATUS = 0x71
|
||||
CMD_TRIGGER = 0xAC
|
||||
CMD_SOFTRESET = 0xBA
|
||||
|
||||
STATUS_BUSY = 0x80
|
||||
STATUS_CALIBRATED = 0x08
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBusy = errors.New("device busy")
|
||||
ErrTimeout = errors.New("timeout")
|
||||
)
|
||||
+17
-16
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a AMG88xx device.
|
||||
@@ -48,7 +49,7 @@ func (d *Device) Configure(cfg Config) {
|
||||
|
||||
// ReadPixels returns the 64 values (8x8 grid) of the sensor converted to millicelsius
|
||||
func (d *Device) ReadPixels(buffer *[64]int16) {
|
||||
d.bus.ReadRegister(uint8(d.Address), PIXEL_OFFSET, d.data)
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), PIXEL_OFFSET, d.data)
|
||||
for i := 0; i < 64; i++ {
|
||||
buffer[i] = int16((uint16(d.data[2*i+1]) << 8) | uint16(d.data[2*i]))
|
||||
if (buffer[i] & (1 << 11)) > 0 { // temperature negative
|
||||
@@ -61,17 +62,17 @@ func (d *Device) ReadPixels(buffer *[64]int16) {
|
||||
|
||||
// SetPCTL sets the PCTL
|
||||
func (d *Device) SetPCTL(pctl uint8) {
|
||||
d.bus.WriteRegister(uint8(d.Address), PCTL, []byte{pctl})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), PCTL, []byte{pctl})
|
||||
}
|
||||
|
||||
// SetReset sets the reset value
|
||||
func (d *Device) SetReset(rst uint8) {
|
||||
d.bus.WriteRegister(uint8(d.Address), RST, []byte{rst})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), RST, []byte{rst})
|
||||
}
|
||||
|
||||
// SetFrameRate configures the frame rate
|
||||
func (d *Device) SetFrameRate(framerate uint8) {
|
||||
d.bus.WriteRegister(uint8(d.Address), FPSC, []byte{framerate & 0x01})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), FPSC, []byte{framerate & 0x01})
|
||||
}
|
||||
|
||||
// SetMovingAverageMode sets the moving average mode
|
||||
@@ -80,7 +81,7 @@ func (d *Device) SetMovingAverageMode(mode bool) {
|
||||
if mode {
|
||||
value = 1
|
||||
}
|
||||
d.bus.WriteRegister(uint8(d.Address), AVE, []byte{value << 5})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), AVE, []byte{value << 5})
|
||||
}
|
||||
|
||||
// SetInterruptLevels sets the interrupt levels
|
||||
@@ -97,8 +98,8 @@ func (d *Device) SetInterruptLevelsHysteresis(high int16, low int16, hysteresis
|
||||
if high > 4095 {
|
||||
high = 4095
|
||||
}
|
||||
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(high & 0xFF)})
|
||||
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((high & 0xFF) >> 4)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8(high & 0xFF)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8((high & 0xFF) >> 4)})
|
||||
|
||||
low = low / PIXEL_TEMP_CONVERSION
|
||||
if low < -4095 {
|
||||
@@ -107,8 +108,8 @@ func (d *Device) SetInterruptLevelsHysteresis(high int16, low int16, hysteresis
|
||||
if low > 4095 {
|
||||
low = 4095
|
||||
}
|
||||
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(low & 0xFF)})
|
||||
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((low & 0xFF) >> 4)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8(low & 0xFF)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8((low & 0xFF) >> 4)})
|
||||
|
||||
hysteresis = hysteresis / PIXEL_TEMP_CONVERSION
|
||||
if hysteresis < -4095 {
|
||||
@@ -117,32 +118,32 @@ func (d *Device) SetInterruptLevelsHysteresis(high int16, low int16, hysteresis
|
||||
if hysteresis > 4095 {
|
||||
hysteresis = 4095
|
||||
}
|
||||
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8(hysteresis & 0xFF)})
|
||||
d.bus.WriteRegister(uint8(d.Address), INTHL, []byte{uint8((hysteresis & 0xFF) >> 4)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8(hysteresis & 0xFF)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTHL, []byte{uint8((hysteresis & 0xFF) >> 4)})
|
||||
}
|
||||
|
||||
// EnableInterrupt enables the interrupt pin on the device
|
||||
func (d *Device) EnableInterrupt() {
|
||||
d.interruptEnable = 1
|
||||
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
|
||||
}
|
||||
|
||||
// DisableInterrupt disables the interrupt pin on the device
|
||||
func (d *Device) DisableInterrupt() {
|
||||
d.interruptEnable = 0
|
||||
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
|
||||
}
|
||||
|
||||
// SetInterruptMode sets the interrupt mode
|
||||
func (d *Device) SetInterruptMode(mode InterruptMode) {
|
||||
d.interruptMode = mode
|
||||
d.bus.WriteRegister(uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), INTC, []byte{((uint8(d.interruptMode) << 1) | d.interruptEnable) & 0x03})
|
||||
}
|
||||
|
||||
// GetInterrupt reads the state of the triggered interrupts
|
||||
func (d *Device) GetInterrupt() []uint8 {
|
||||
data := make([]uint8, 8)
|
||||
d.bus.ReadRegister(uint8(d.Address), INT_OFFSET, data)
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), INT_OFFSET, data)
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -154,6 +155,6 @@ func (d *Device) ClearInterrupt() {
|
||||
// ReadThermistor reads the onboard thermistor
|
||||
func (d *Device) ReadThermistor() int16 {
|
||||
data := make([]uint8, 2)
|
||||
d.bus.ReadRegister(uint8(d.Address), TTHL, data)
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), TTHL, data)
|
||||
return (int16((uint16(data[1])<<8)|uint16(data[0])) * THERMISTOR_CONVERSION) / 10
|
||||
}
|
||||
|
||||
+28
-28
@@ -5,7 +5,10 @@ package apa102 // import "tinygo.org/x/drivers/apa102"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -23,54 +26,51 @@ var startFrame = []byte{0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
// Device wraps APA102 SPI LEDs.
|
||||
type Device struct {
|
||||
bus SPI
|
||||
bus drivers.SPI
|
||||
Order int
|
||||
}
|
||||
|
||||
// The SPI interface specifies the minimum functionality that a bus
|
||||
// implementation needs to provide for use by the APA102 driver. Hardware
|
||||
// SPI from the TinyGo "machine" package implements this already.
|
||||
type SPI interface {
|
||||
Tx(w, r []byte) error
|
||||
Transfer(b byte) (byte, error)
|
||||
buf [4]byte
|
||||
}
|
||||
|
||||
// New returns a new APA102 driver. Pass in a fully configured SPI bus.
|
||||
func New(b SPI) Device {
|
||||
return Device{bus: b, Order: BGR}
|
||||
func New(b drivers.SPI) *Device {
|
||||
return &Device{bus: b, Order: BGR}
|
||||
}
|
||||
|
||||
// NewSoftwareSPI returns a new APA102 driver that will use a software based
|
||||
// implementation of the SPI protocol.
|
||||
func NewSoftwareSPI(sckPin, sdoPin machine.Pin, delay uint32) Device {
|
||||
return New(&bbSPI{SCK: sckPin, SDO: sdoPin, Delay: delay})
|
||||
func NewSoftwareSPI(sckPin, sdoPin pin.Output, delay uint32) *Device {
|
||||
return New(&bbSPI{SCK: sckPin.Set, SDO: sdoPin.Set, Delay: delay, configurePins: func() {
|
||||
legacy.ConfigurePinOut(sckPin)
|
||||
legacy.ConfigurePinOut(sdoPin)
|
||||
}})
|
||||
}
|
||||
|
||||
// WriteColors writes the given RGBA color slice out using the APA102 protocol.
|
||||
// The A value (Alpha channel) is used for brightness, set to 0xff (255) for maximum.
|
||||
func (d Device) WriteColors(cs []color.RGBA) (n int, err error) {
|
||||
func (d *Device) WriteColors(cs []color.RGBA) (n int, err error) {
|
||||
d.startFrame()
|
||||
|
||||
// write data
|
||||
for _, c := range cs {
|
||||
// brightness is scaled to 5 bit value
|
||||
d.bus.Transfer(0xe0 | (c.A >> 3))
|
||||
d.buf[0] = 0xe0 | (c.A >> 3)
|
||||
|
||||
// set the colors
|
||||
switch d.Order {
|
||||
case BRG:
|
||||
d.bus.Transfer(c.B)
|
||||
d.bus.Transfer(c.R)
|
||||
d.bus.Transfer(c.G)
|
||||
d.buf[1] = c.B
|
||||
d.buf[2] = c.R
|
||||
d.buf[3] = c.G
|
||||
case GRB:
|
||||
d.bus.Transfer(c.G)
|
||||
d.bus.Transfer(c.R)
|
||||
d.bus.Transfer(c.B)
|
||||
d.buf[1] = c.G
|
||||
d.buf[2] = c.R
|
||||
d.buf[3] = c.B
|
||||
case BGR:
|
||||
d.bus.Transfer(c.B)
|
||||
d.bus.Transfer(c.G)
|
||||
d.bus.Transfer(c.R)
|
||||
d.buf[1] = c.B
|
||||
d.buf[2] = c.G
|
||||
d.buf[3] = c.R
|
||||
}
|
||||
d.bus.Tx(d.buf[:], nil)
|
||||
}
|
||||
|
||||
d.endFrame(len(cs))
|
||||
@@ -79,7 +79,7 @@ func (d Device) WriteColors(cs []color.RGBA) (n int, err error) {
|
||||
}
|
||||
|
||||
// Write the raw bytes using the APA102 protocol.
|
||||
func (d Device) Write(buf []byte) (n int, err error) {
|
||||
func (d *Device) Write(buf []byte) (n int, err error) {
|
||||
d.startFrame()
|
||||
d.bus.Tx(buf, nil)
|
||||
d.endFrame(len(buf) / 4)
|
||||
@@ -88,14 +88,14 @@ func (d Device) Write(buf []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// startFrame sends the start bytes for a strand of LEDs.
|
||||
func (d Device) startFrame() {
|
||||
func (d *Device) startFrame() {
|
||||
d.bus.Tx(startFrame, nil)
|
||||
}
|
||||
|
||||
// endFrame sends the end frame marker with one extra bit per LED so
|
||||
// long strands of LEDs receive the necessary termination for updates.
|
||||
// See https://cpldcpu.wordpress.com/2014/11/30/understanding-the-apa102-superled/
|
||||
func (d Device) endFrame(count int) {
|
||||
func (d *Device) endFrame(count int) {
|
||||
for i := 0; i < count/16; i++ {
|
||||
d.bus.Transfer(0xff)
|
||||
}
|
||||
|
||||
+12
-6
@@ -1,6 +1,9 @@
|
||||
package apa102
|
||||
|
||||
import "machine"
|
||||
import (
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// bbSPI is a dumb bit-bang implementation of SPI protocol that is hardcoded
|
||||
// to mode 0 and ignores trying to receive data. Just enough for the APA102.
|
||||
@@ -8,15 +11,18 @@ import "machine"
|
||||
// most purposes other than the APA102 package. It might be desirable to make
|
||||
// this more generic and include it in the TinyGo "machine" package instead.
|
||||
type bbSPI struct {
|
||||
SCK machine.Pin
|
||||
SDO machine.Pin
|
||||
Delay uint32
|
||||
SCK pin.OutputFunc
|
||||
SDO pin.OutputFunc
|
||||
Delay uint32
|
||||
configurePins func()
|
||||
}
|
||||
|
||||
// Configure sets up the SCK and SDO pins as outputs and sets them low
|
||||
func (s *bbSPI) Configure() {
|
||||
s.SCK.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
s.SDO.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
if s.configurePins == nil {
|
||||
panic(legacy.ErrConfigBeforeInstantiated)
|
||||
}
|
||||
s.configurePins()
|
||||
s.SCK.Low()
|
||||
s.SDO.Low()
|
||||
if s.Delay == 0 {
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
// Package apds9960 implements a driver for APDS-9960,
|
||||
// a digital proximity, ambient light, RGB and gesture sensor.
|
||||
//
|
||||
// Datasheet: https://cdn.sparkfun.com/assets/learn_tutorials/3/2/1/Avago-APDS-9960-datasheet.pdf
|
||||
package apds9960
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a APDS-9960 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint8
|
||||
mode uint8
|
||||
gesture gestureData
|
||||
}
|
||||
|
||||
// Configuration for APDS-9960 device.
|
||||
type Configuration struct {
|
||||
ProximityPulseLength uint8
|
||||
ProximityPulseCount uint8
|
||||
GesturePulseLength uint8
|
||||
GesturePulseCount uint8
|
||||
ProximityGain uint8
|
||||
GestureGain uint8
|
||||
ColorGain uint8
|
||||
ADCIntegrationCycles uint16
|
||||
LEDBoost uint16
|
||||
threshold uint8
|
||||
sensitivity uint8
|
||||
}
|
||||
|
||||
// for gesture-related data
|
||||
type gestureData struct {
|
||||
detected uint8
|
||||
threshold uint8
|
||||
sensitivity uint8
|
||||
gXDelta int16
|
||||
gYDelta int16
|
||||
gXPrevDelta int16
|
||||
gYPrevDelta int16
|
||||
received bool
|
||||
}
|
||||
|
||||
// for enabling various device function
|
||||
type enableConfig struct {
|
||||
GEN bool
|
||||
PIEN bool
|
||||
AIEN bool
|
||||
WEN bool
|
||||
PEN bool
|
||||
AEN bool
|
||||
PON bool
|
||||
}
|
||||
|
||||
// New creates a new APDS-9960 connection. The I2C bus must already be
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{bus: bus, Address: ADPS9960_ADDRESS, mode: MODE_NONE}
|
||||
}
|
||||
|
||||
// Connected returns whether APDS-9960 has been found.
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_ID_REG, data)
|
||||
return data[0] == 0xAB
|
||||
}
|
||||
|
||||
// GetMode returns current engine mode
|
||||
func (d *Device) GetMode() uint8 {
|
||||
return d.mode
|
||||
}
|
||||
|
||||
// DisableAll turns off the device and all functions
|
||||
func (d *Device) DisableAll() {
|
||||
d.enable(enableConfig{})
|
||||
legacy.WriteRegister(d.bus, d.Address, APDS9960_GCONF4_REG, []byte{0x00})
|
||||
d.mode = MODE_NONE
|
||||
d.gesture.detected = GESTURE_NONE
|
||||
}
|
||||
|
||||
// SetProximityPulse sets proximity pulse length (4, 8, 16, 32) and count (1~64)
|
||||
// default: 16, 64
|
||||
func (d *Device) SetProximityPulse(length, count uint8) {
|
||||
legacy.WriteRegister(d.bus, d.Address, APDS9960_PPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
|
||||
}
|
||||
|
||||
// SetGesturePulse sets gesture pulse length (4, 8, 16, 32) and count (1~64)
|
||||
// default: 16, 64
|
||||
func (d *Device) SetGesturePulse(length, count uint8) {
|
||||
legacy.WriteRegister(d.bus, d.Address, APDS9960_GPULSE_REG, []byte{getPulseLength(length)<<6 | getPulseCount(count)})
|
||||
}
|
||||
|
||||
// SetADCIntegrationCycles sets ALS/color ADC internal integration cycles (1~256, 1 cycle = 2.78 ms)
|
||||
// default: 4 (~10 ms)
|
||||
func (d *Device) SetADCIntegrationCycles(cycles uint16) {
|
||||
if cycles > 256 {
|
||||
cycles = 256
|
||||
}
|
||||
legacy.WriteRegister(d.bus, d.Address, APDS9960_ATIME_REG, []byte{uint8(256 - cycles)})
|
||||
}
|
||||
|
||||
// SetGains sets proximity/gesture gain (1, 2, 4, 8x) and ALS/color gain (1, 4, 16, 64x)
|
||||
// default: 1, 1, 4
|
||||
func (d *Device) SetGains(proximityGain, gestureGain, colorGain uint8) {
|
||||
legacy.WriteRegister(d.bus, d.Address, APDS9960_CONTROL_REG, []byte{getProximityGain(proximityGain)<<2 | getALSGain(colorGain)})
|
||||
legacy.WriteRegister(d.bus, d.Address, APDS9960_GCONF2_REG, []byte{getProximityGain(gestureGain) << 5})
|
||||
}
|
||||
|
||||
// LEDBoost sets proximity and gesture LED current level (100, 150, 200, 300 (%))
|
||||
// default: 100
|
||||
func (d *Device) LEDBoost(percent uint16) {
|
||||
var v uint8
|
||||
switch percent {
|
||||
case 100:
|
||||
v = 0
|
||||
case 150:
|
||||
v = 1
|
||||
case 200:
|
||||
v = 2
|
||||
case 300:
|
||||
v = 3
|
||||
}
|
||||
legacy.WriteRegister(d.bus, d.Address, APDS9960_CONFIG2_REG, []byte{0x01 | v<<4})
|
||||
}
|
||||
|
||||
// Setthreshold sets threshold (0~255) for detecting gestures
|
||||
// default: 30
|
||||
func (d *Device) Setthreshold(t uint8) {
|
||||
d.gesture.threshold = t
|
||||
}
|
||||
|
||||
// Setsensitivity sets sensivity (0~100) for detecting gestures
|
||||
// default: 20
|
||||
func (d *Device) Setsensitivity(s uint8) {
|
||||
if s > 100 {
|
||||
s = 100
|
||||
}
|
||||
d.gesture.sensitivity = 100 - s
|
||||
}
|
||||
|
||||
// EnableProximity starts the proximity engine
|
||||
func (d *Device) EnableProximity() {
|
||||
if d.mode != MODE_NONE {
|
||||
d.DisableAll()
|
||||
}
|
||||
d.enable(enableConfig{PON: true, PEN: true, WEN: true})
|
||||
d.mode = MODE_PROXIMITY
|
||||
}
|
||||
|
||||
// ProximityAvailable reports if proximity data is available
|
||||
func (d *Device) ProximityAvailable() bool {
|
||||
if d.mode == MODE_PROXIMITY && d.readStatus("PVALID") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ReadProximity reads proximity data (0~255)
|
||||
func (d *Device) ReadProximity() (proximity int32) {
|
||||
if d.mode != MODE_PROXIMITY {
|
||||
return 0
|
||||
}
|
||||
data := []byte{0}
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_PDATA_REG, data)
|
||||
return 255 - int32(data[0])
|
||||
}
|
||||
|
||||
// EnableColor starts the color engine
|
||||
func (d *Device) EnableColor() {
|
||||
if d.mode != MODE_NONE {
|
||||
d.DisableAll()
|
||||
}
|
||||
d.enable(enableConfig{PON: true, AEN: true, WEN: true})
|
||||
d.mode = MODE_COLOR
|
||||
}
|
||||
|
||||
// ColorAvailable reports if color data is available
|
||||
func (d *Device) ColorAvailable() bool {
|
||||
if d.mode == MODE_COLOR && d.readStatus("AVALID") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ReadColor reads color data (red, green, blue, clear color/brightness)
|
||||
func (d *Device) ReadColor() (r int32, g int32, b int32, clear int32) {
|
||||
if d.mode != MODE_COLOR {
|
||||
return
|
||||
}
|
||||
data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_CDATAL_REG, data[:1])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_CDATAH_REG, data[1:2])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_RDATAL_REG, data[2:3])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_RDATAH_REG, data[3:4])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_GDATAL_REG, data[4:5])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_GDATAH_REG, data[5:6])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_BDATAL_REG, data[6:7])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_BDATAH_REG, data[7:])
|
||||
clear = int32(uint16(data[1])<<8 | uint16(data[0]))
|
||||
r = int32(uint16(data[3])<<8 | uint16(data[2]))
|
||||
g = int32(uint16(data[5])<<8 | uint16(data[4]))
|
||||
b = int32(uint16(data[7])<<8 | uint16(data[6]))
|
||||
return
|
||||
}
|
||||
|
||||
// EnableGesture starts the gesture engine
|
||||
func (d *Device) EnableGesture() {
|
||||
if d.mode != MODE_NONE {
|
||||
d.DisableAll()
|
||||
}
|
||||
d.enable(enableConfig{PON: true, PEN: true, GEN: true, WEN: true})
|
||||
d.mode = MODE_GESTURE
|
||||
d.gesture.detected = GESTURE_NONE
|
||||
d.gesture.gXDelta = 0
|
||||
d.gesture.gYDelta = 0
|
||||
d.gesture.gXPrevDelta = 0
|
||||
d.gesture.gYPrevDelta = 0
|
||||
d.gesture.received = false
|
||||
}
|
||||
|
||||
// GestureAvailable reports if gesture data is available
|
||||
func (d *Device) GestureAvailable() bool {
|
||||
if d.mode != MODE_GESTURE {
|
||||
return false
|
||||
}
|
||||
|
||||
data := []byte{0, 0, 0, 0}
|
||||
|
||||
// check GVALID
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_GSTATUS_REG, data[:1])
|
||||
if data[0]&0x01 == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// get number of data sets available in FIFO
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFLVL_REG, data[:1])
|
||||
availableDataSets := data[0]
|
||||
if availableDataSets == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// read up, down, left and right proximity data from FIFO
|
||||
var dataSets [32][4]uint8
|
||||
for i := uint8(0); i < availableDataSets; i++ {
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_U_REG, data[:1])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_D_REG, data[1:2])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_L_REG, data[2:3])
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_GFIFO_R_REG, data[3:4])
|
||||
for j := uint8(0); j < 4; j++ {
|
||||
dataSets[i][j] = data[j]
|
||||
}
|
||||
}
|
||||
|
||||
// gesture detection process
|
||||
d.gesture.detected = GESTURE_NONE
|
||||
for i := uint8(0); i < availableDataSets; i++ {
|
||||
U := dataSets[i][0]
|
||||
D := dataSets[i][1]
|
||||
L := dataSets[i][2]
|
||||
R := dataSets[i][3]
|
||||
|
||||
// if all readings fall below threshold, it's possible that
|
||||
// a movement's just been made
|
||||
if U < d.gesture.threshold && D < d.gesture.threshold && L < d.gesture.threshold && R < d.gesture.threshold {
|
||||
d.gesture.received = true
|
||||
// if there were movement in the previous step (including the last data sets)
|
||||
if d.gesture.gXPrevDelta != 0 && d.gesture.gYPrevDelta != 0 {
|
||||
totalX := d.gesture.gXPrevDelta - d.gesture.gXDelta
|
||||
totalY := d.gesture.gYPrevDelta - d.gesture.gYDelta
|
||||
// if previous and current movement are in opposite directions (pass through one led then next)
|
||||
// and the difference is big enough, the gesture is recorded
|
||||
switch {
|
||||
case totalX < -int16(d.gesture.sensitivity):
|
||||
d.gesture.detected = GESTURE_LEFT
|
||||
case totalX > int16(d.gesture.sensitivity):
|
||||
d.gesture.detected = GESTURE_RIGHT
|
||||
case totalY > int16(d.gesture.sensitivity):
|
||||
d.gesture.detected = GESTURE_DOWN
|
||||
case totalY < -int16(d.gesture.sensitivity):
|
||||
d.gesture.detected = GESTURE_UP
|
||||
}
|
||||
d.gesture.gXDelta = 0
|
||||
d.gesture.gYDelta = 0
|
||||
d.gesture.gXPrevDelta = 0
|
||||
d.gesture.gYPrevDelta = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// recording current movement
|
||||
d.gesture.gXDelta = int16(R) - int16(L)
|
||||
d.gesture.gYDelta = int16(D) - int16(U)
|
||||
if d.gesture.received {
|
||||
d.gesture.received = false
|
||||
d.gesture.gXPrevDelta = d.gesture.gXDelta
|
||||
d.gesture.gYPrevDelta = d.gesture.gYDelta
|
||||
}
|
||||
}
|
||||
|
||||
return d.gesture.detected != GESTURE_NONE
|
||||
}
|
||||
|
||||
// ReadGesture reads last gesture data
|
||||
func (d *Device) ReadGesture() (gesture int32) {
|
||||
return int32(d.gesture.detected)
|
||||
}
|
||||
|
||||
// private functions
|
||||
|
||||
func (d *Device) configureDevice(cfg Configuration) {
|
||||
d.DisableAll() // turn off everything
|
||||
|
||||
// "default" settings
|
||||
if cfg.ProximityPulseLength == 0 {
|
||||
cfg.ProximityPulseLength = 16
|
||||
}
|
||||
if cfg.ProximityPulseCount == 0 {
|
||||
cfg.ProximityPulseCount = 64
|
||||
}
|
||||
if cfg.GesturePulseLength == 0 {
|
||||
cfg.GesturePulseLength = 16
|
||||
}
|
||||
if cfg.GesturePulseCount == 0 {
|
||||
cfg.GesturePulseCount = 64
|
||||
}
|
||||
if cfg.ProximityGain == 0 {
|
||||
cfg.ProximityGain = 1
|
||||
}
|
||||
if cfg.GestureGain == 0 {
|
||||
cfg.GestureGain = 1
|
||||
}
|
||||
if cfg.ColorGain == 0 {
|
||||
cfg.ColorGain = 4
|
||||
}
|
||||
if cfg.ADCIntegrationCycles == 0 {
|
||||
cfg.ADCIntegrationCycles = 4
|
||||
}
|
||||
if cfg.threshold == 0 {
|
||||
d.gesture.threshold = 30
|
||||
}
|
||||
if cfg.sensitivity == 0 {
|
||||
d.gesture.sensitivity = 20
|
||||
}
|
||||
|
||||
d.SetProximityPulse(cfg.ProximityPulseLength, cfg.ProximityPulseCount)
|
||||
d.SetGesturePulse(cfg.GesturePulseLength, cfg.GesturePulseCount)
|
||||
d.SetGains(cfg.ProximityGain, cfg.GestureGain, cfg.ColorGain)
|
||||
d.SetADCIntegrationCycles(cfg.ADCIntegrationCycles)
|
||||
|
||||
if cfg.LEDBoost > 0 {
|
||||
d.LEDBoost(cfg.LEDBoost)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) enable(cfg enableConfig) {
|
||||
var gen, pien, aien, wen, pen, aen, pon uint8
|
||||
|
||||
if cfg.GEN {
|
||||
gen = 1
|
||||
}
|
||||
if cfg.PIEN {
|
||||
pien = 1
|
||||
}
|
||||
if cfg.AIEN {
|
||||
aien = 1
|
||||
}
|
||||
if cfg.WEN {
|
||||
wen = 1
|
||||
}
|
||||
if cfg.PEN {
|
||||
pen = 1
|
||||
}
|
||||
if cfg.AEN {
|
||||
aen = 1
|
||||
}
|
||||
if cfg.PON {
|
||||
pon = 1
|
||||
}
|
||||
|
||||
data := []byte{gen<<6 | pien<<5 | aien<<4 | wen<<3 | pen<<2 | aen<<1 | pon}
|
||||
legacy.WriteRegister(d.bus, d.Address, APDS9960_ENABLE_REG, data)
|
||||
|
||||
if cfg.PON {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) readStatus(param string) bool {
|
||||
data := []byte{0}
|
||||
legacy.ReadRegister(d.bus, d.Address, APDS9960_STATUS_REG, data)
|
||||
|
||||
switch param {
|
||||
case "CPSAT":
|
||||
return data[0]>>7&0x01 == 1
|
||||
case "PGSAT":
|
||||
return data[0]>>6&0x01 == 1
|
||||
case "PINT":
|
||||
return data[0]>>5&0x01 == 1
|
||||
case "AINT":
|
||||
return data[0]>>4&0x01 == 1
|
||||
case "PVALID":
|
||||
return data[0]>>1&0x01 == 1
|
||||
case "AVALID":
|
||||
return data[0]&0x01 == 1
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func getPulseLength(l uint8) uint8 {
|
||||
switch l {
|
||||
case 4:
|
||||
return 0
|
||||
case 8:
|
||||
return 1
|
||||
case 16:
|
||||
return 2
|
||||
case 32:
|
||||
return 3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func getPulseCount(c uint8) uint8 {
|
||||
if c < 1 && c > 64 {
|
||||
return 0
|
||||
}
|
||||
return c - 1
|
||||
}
|
||||
|
||||
func getProximityGain(g uint8) uint8 {
|
||||
switch g {
|
||||
case 1:
|
||||
return 0
|
||||
case 2:
|
||||
return 1
|
||||
case 4:
|
||||
return 2
|
||||
case 8:
|
||||
return 3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func getALSGain(g uint8) uint8 {
|
||||
switch g {
|
||||
case 1:
|
||||
return 0
|
||||
case 4:
|
||||
return 1
|
||||
case 16:
|
||||
return 2
|
||||
case 64:
|
||||
return 3
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !nano_33_ble
|
||||
|
||||
package apds9960
|
||||
|
||||
// Configure sets up the APDS-9960 device.
|
||||
func (d *Device) Configure(cfg Configuration) {
|
||||
// configure device
|
||||
d.configureDevice(cfg)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
//go:build nano_33_ble
|
||||
|
||||
package apds9960
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Configure sets up the APDS-9960 device.
|
||||
func (d *Device) Configure(cfg Configuration) {
|
||||
|
||||
// Following lines are Nano 33 BLE specific, they have nothing to do with sensor per se
|
||||
machine.LSM_PWR.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
machine.LSM_PWR.High()
|
||||
machine.I2C_PULLUP.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
machine.I2C_PULLUP.High()
|
||||
// Wait a moment
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// configure device
|
||||
d.configureDevice(cfg)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package apds9960
|
||||
|
||||
const (
|
||||
|
||||
// I2C address
|
||||
ADPS9960_ADDRESS = 0x39
|
||||
|
||||
// control/status registers
|
||||
APDS9960_RAM_REG = 0x00
|
||||
APDS9960_ENABLE_REG = 0x80
|
||||
APDS9960_ATIME_REG = 0x81
|
||||
APDS9960_WTIME_REG = 0x83
|
||||
APDS9960_AILTIL_REG = 0x84
|
||||
APDS9960_AILTH_REG = 0x85
|
||||
APDS9960_AIHTL_REG = 0x86
|
||||
APDS9960_AIHTH_REG = 0x87
|
||||
APDS9960_PILT_REG = 0x89
|
||||
APDS9960_PIHT_REG = 0x8B
|
||||
APDS9960_PERS_REG = 0x8C
|
||||
APDS9960_CONFIG1_REG = 0x8D
|
||||
APDS9960_PPULSE_REG = 0x8E
|
||||
APDS9960_CONTROL_REG = 0x8F
|
||||
APDS9960_CONFIG2_REG = 0x90
|
||||
APDS9960_ID_REG = 0x92
|
||||
APDS9960_STATUS_REG = 0x93
|
||||
APDS9960_CDATAL_REG = 0x94
|
||||
APDS9960_CDATAH_REG = 0x95
|
||||
APDS9960_RDATAL_REG = 0x96
|
||||
APDS9960_RDATAH_REG = 0x97
|
||||
APDS9960_GDATAL_REG = 0x98
|
||||
APDS9960_GDATAH_REG = 0x99
|
||||
APDS9960_BDATAL_REG = 0x9A
|
||||
APDS9960_BDATAH_REG = 0x9B
|
||||
APDS9960_PDATA_REG = 0x9C
|
||||
APDS9960_POFFSET_UR_REG = 0x9D
|
||||
APDS9960_POFFSET_DL_REG = 0x9E
|
||||
APDS9960_CONFIG3_REG = 0x9F
|
||||
APDS9960_GPENTH_REG = 0xA0
|
||||
APDS9960_GEXTH_REG = 0xA1
|
||||
APDS9960_GCONF1_REG = 0xA2
|
||||
APDS9960_GCONF2_REG = 0xA3
|
||||
APDS9960_GOFFSET_U_REG = 0xA4
|
||||
APDS9960_GOFFSET_D_REG = 0xA5
|
||||
APDS9960_GOFFSET_L_REG = 0xA7
|
||||
APDS9960_GOFFSET_R_REG = 0xA9
|
||||
APDS9960_GPULSE_REG = 0xA6
|
||||
APDS9960_GCONF3_REG = 0xAA
|
||||
APDS9960_GCONF4_REG = 0xAB
|
||||
APDS9960_GFLVL_REG = 0xAE
|
||||
APDS9960_GSTATUS_REG = 0xAF
|
||||
APDS9960_IFORCE_REG = 0xE4
|
||||
APDS9960_PICLEAR_REG = 0xE5
|
||||
APDS9960_CICLEAR_REG = 0xE6
|
||||
APDS9960_AICLEAR_REG = 0xE7
|
||||
APDS9960_GFIFO_U_REG = 0xFC
|
||||
APDS9960_GFIFO_D_REG = 0xFD
|
||||
APDS9960_GFIFO_L_REG = 0xFE
|
||||
APDS9960_GFIFO_R_REG = 0xFF
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// sensor modes
|
||||
MODE_NONE = iota
|
||||
MODE_PROXIMITY
|
||||
MODE_COLOR
|
||||
MODE_GESTURE
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// detected gestures
|
||||
GESTURE_NONE = iota
|
||||
GESTURE_UP
|
||||
GESTURE_DOWN
|
||||
GESTURE_LEFT
|
||||
GESTURE_RIGHT
|
||||
)
|
||||
@@ -0,0 +1,172 @@
|
||||
// Product: https://ams.com/as5600
|
||||
// Datasheet: https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf
|
||||
|
||||
package as560x // import tinygo.org/x/drivers/ams560x
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// AS5600 includes MPOS & MANG in addition to ZPOS to set a 'narrower angle range'
|
||||
// ZPOS enables setting the 'zero position' of the device to any RAW_ANGLE value.
|
||||
// MPOS ('max position') & MANG 'max angle' enable a 'partial range' on the AS5600.
|
||||
// The value in ANGLE is scaled & adjusted by the device according to ZPOS and MPOS/MANG.
|
||||
// The entire 12-bit range is 'compressed' into the RAW_ANGLE range of ZPOS->MPOS
|
||||
// (or ZPOS->ZPOS+MANG) thus enabling a higher resolution for a partial range.
|
||||
// if ZPOS > MPOS (or ZPOS + MANG > 4095) i.e. the incremental range 'crosses zero'
|
||||
// then the device will automatically compensate for the correct range.
|
||||
// For RAW_ANGLE values outside of the partial range, ANGLE will be 'capped' at either
|
||||
// 0 or 4095, depending on 'which end of the partial range is closer.'
|
||||
|
||||
// AS5600Device represents an ams AS5600 device driver accessed over I2C
|
||||
type AS5600Device struct {
|
||||
// promote BaseDevice
|
||||
BaseDevice
|
||||
}
|
||||
|
||||
// NewAS5600 creates a new AS5600Device given an I2C bus
|
||||
func NewAS5600(bus drivers.I2C) AS5600Device {
|
||||
// Create base device
|
||||
baseDev := newBaseDevice(bus)
|
||||
// Add AS5600 specific registers
|
||||
baseDev.registers[MPOS] = newI2CRegister(MPOS, 0, 0xfff, 2, reg_read|reg_write|reg_program)
|
||||
baseDev.registers[MANG] = newI2CRegister(MANG, 0, 0xfff, 2, reg_read|reg_write|reg_program)
|
||||
// Add AS5600 specific 'virtual registers'
|
||||
conf, ok := baseDev.registers[CONF]
|
||||
if ok {
|
||||
baseDev.registers[PWMF] = newVirtualRegister(conf, 6, 0b11)
|
||||
baseDev.registers[OUTS] = newVirtualRegister(conf, 4, 0b11)
|
||||
}
|
||||
// Return the device
|
||||
return AS5600Device{baseDev}
|
||||
}
|
||||
|
||||
// Configure sets up the AMS AS5600 sensor device with the given configuration.
|
||||
func (d *AS5600Device) Configure(cfg Config) error {
|
||||
// Call the BaseDevice method to do the actual Configure
|
||||
d.BaseDevice.Configure(cfg)
|
||||
// For AS5600 devices we need to calculate the maxAngle on startup from ZPOS/MPOS/MANG
|
||||
// These could have been permanently BURN'ed (by writing BURN register with BURN_ANGLE/BURN_SETTING)
|
||||
// or may have already been written in previous runs without a power cycle since.
|
||||
mpos, err := d.ReadRegister(MPOS)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
mang, err := d.ReadRegister(MANG)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
// Read ZPOS for side effect of caching only so that next calculateEffectiveMaxAngle() can't fail
|
||||
if _, err = d.ReadRegister(ZPOS); nil != err {
|
||||
return err
|
||||
}
|
||||
if mpos != 0 {
|
||||
// If MPOS is set, use MPOS regardless of MANG
|
||||
err = d.calculateEffectiveMaxAngle(MPOS, mpos)
|
||||
} else if mang != 0 {
|
||||
// If MANG is set and MPOS == 0, use MANG
|
||||
err = d.calculateEffectiveMaxAngle(MANG, mang)
|
||||
} else {
|
||||
// if neither is set, we have no narrow range
|
||||
d.maxAngle = NATIVE_ANGLE_RANGE
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// calculateEffectiveMaxAngle calculates d.maxAngle after one of ZPOS/MPOS/MANG have been written
|
||||
func (d *AS5600Device) calculateEffectiveMaxAngle(register uint8, value uint16) error {
|
||||
|
||||
var zpos, mpos uint16 = 0, 0
|
||||
var err error = nil
|
||||
|
||||
switch register {
|
||||
case MANG:
|
||||
d.maxAngle = value // The easy case
|
||||
return nil
|
||||
case ZPOS:
|
||||
zpos = value
|
||||
mpos, err = d.ReadRegister(MPOS)
|
||||
case MPOS:
|
||||
mpos = value
|
||||
zpos, err = d.ReadRegister(ZPOS)
|
||||
default:
|
||||
panic("calculateEffectiveMaxAngle() can only work from ZPOS, MPOS or MANG")
|
||||
}
|
||||
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
// MANG is effectively MPOS-ZPOS
|
||||
mang := int(mpos) - int(zpos)
|
||||
// correct for mpos < zpos
|
||||
if mang < 0 {
|
||||
mang += NATIVE_ANGLE_RANGE
|
||||
}
|
||||
d.maxAngle = uint16(mang)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegister writes the given value for the given register to the AS560x device via I2C
|
||||
func (d *AS5600Device) WriteRegister(address uint8, value uint16) error {
|
||||
// Call the BaseDevice method to do the actual write
|
||||
if err := d.BaseDevice.WriteRegister(address, value); err != nil {
|
||||
return err
|
||||
}
|
||||
// When either ZPOS/MANG/MPOS are set we need to recalculate maxAngle
|
||||
// We also may need to invalidate some cached values for the other two registers
|
||||
recalc := false
|
||||
switch address {
|
||||
case ZPOS:
|
||||
// Setting a new ZPOS invalidates MPOS but not MANG
|
||||
d.registers[MPOS].invalidate()
|
||||
recalc = true
|
||||
case MPOS:
|
||||
// Setting a new MPOS invalidates MANG but not ZPOS
|
||||
d.registers[MANG].invalidate()
|
||||
recalc = true
|
||||
case MANG:
|
||||
// Setting a new MANG invalidates MPOS but not ZPOS
|
||||
d.registers[MPOS].invalidate()
|
||||
recalc = true
|
||||
}
|
||||
if recalc {
|
||||
// Datasheet tells us to wait at least 1ms before reading back
|
||||
time.Sleep(time.Millisecond * 10) // conservative wait
|
||||
return d.calculateEffectiveMaxAngle(address, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMaxPosition returns the 'max position' (MPOS) in different units
|
||||
func (d *AS5600Device) GetMaxPosition(units AngleUnit) (uint16, float32, error) {
|
||||
mpos, err := d.ReadRegister(MPOS)
|
||||
if nil != err {
|
||||
return 0, 0.0, err
|
||||
}
|
||||
// Convert to requested units
|
||||
i, f := convertFromNativeAngle(mpos, NATIVE_ANGLE_RANGE, units)
|
||||
return i, f, nil
|
||||
}
|
||||
|
||||
// SetMaxPosition sets the 'max position' (MPOS) in different units
|
||||
func (d *AS5600Device) SetMaxPosition(mpos float32, units AngleUnit) error {
|
||||
return d.WriteRegister(MPOS, convertToNativeAngle(mpos, units))
|
||||
}
|
||||
|
||||
// GetMaxAngle returns the 'max position' (MANG) in different units
|
||||
func (d *AS5600Device) GetMaxAngle(units AngleUnit) (uint16, float32, error) {
|
||||
mang, err := d.ReadRegister(MANG)
|
||||
if nil != err {
|
||||
return 0, 0.0, err
|
||||
}
|
||||
// Convert to requested units
|
||||
i, f := convertFromNativeAngle(mang, NATIVE_ANGLE_RANGE, units)
|
||||
return i, f, nil
|
||||
}
|
||||
|
||||
// SetMaxAngle sets the 'max angle' (MANG) in different units
|
||||
func (d *AS5600Device) SetMaxAngle(mang float32, units AngleUnit) error {
|
||||
return d.WriteRegister(MANG, convertToNativeAngle(mang, units))
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Product: https://ams.com/as5601
|
||||
// Datasheet: https://ams.com/documents/20143/36005/AS5601_DS000395_3-00.pdf
|
||||
|
||||
package as560x // import tinygo.org/x/drivers/ams560x
|
||||
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
// AS5601Device represents an ams AS5601 device driver accessed over I2C
|
||||
type AS5601Device struct {
|
||||
BaseDevice // promote base device
|
||||
}
|
||||
|
||||
// NewAS5601 creates a new AS5601Device given an I2C bus
|
||||
func NewAS5601(bus drivers.I2C) AS5601Device {
|
||||
// Create base device
|
||||
baseDev := newBaseDevice(bus)
|
||||
// Add AS5601 specific registers
|
||||
baseDev.registers[ABN] = newI2CRegister(ABN, 0, 0b1111, 1, reg_read|reg_write|reg_program)
|
||||
baseDev.registers[PUSHTHR] = newI2CRegister(PUSHTHR, 0, 0xff, 1, reg_read|reg_write|reg_program)
|
||||
// Return the device
|
||||
return AS5601Device{baseDev}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
// Package as560x implements drivers for the ams AS5600/AS5601 on-axis magnetic rotary position sensors
|
||||
//
|
||||
// Product Pages:
|
||||
// AS5600: https://ams.com/as5600
|
||||
// AS5601: https://ams.com/as5601
|
||||
//
|
||||
// Datasheets:
|
||||
// AS5600: https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf
|
||||
// AS5601: https://ams.com/documents/20143/36005/AS5601_DS000395_3-00.pdf
|
||||
//
|
||||
|
||||
package as560x // import tinygo.org/x/drivers/ams560x
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Config holds the configuration for the AMS AS560x sensor devices.
|
||||
type Config struct {
|
||||
// Address is the I2C address of the AS560x device. If left zero this will default to 0x36
|
||||
Address uint8
|
||||
}
|
||||
|
||||
// MagnetStrength is an enum to indicate the magnetic field strength detected by the AS560x sensors.
|
||||
type MagnetStrength int
|
||||
|
||||
const (
|
||||
// MagnetTooWeak indicates that the magnet strength is too weak (AGC maximum gain overflow) - move it closer
|
||||
MagnetTooWeak MagnetStrength = iota - 1
|
||||
// MagnetOk indicates that the magnet strength is about right.
|
||||
MagnetOk
|
||||
// MagnetTooStrong indicates that the magnet strength is too strong (AGC minimum gain overflow) - move it further away
|
||||
MagnetTooStrong
|
||||
)
|
||||
|
||||
// AngleUnit is an enum to allow the use of different units when reading/writing angles from the AS560x sensors.
|
||||
type AngleUnit int
|
||||
|
||||
const (
|
||||
// ANGLE_NATIVE uses the device's native angle measurement. i.e. 12-bit integer, 0 <= angle <= 0xfff (4095)
|
||||
ANGLE_NATIVE AngleUnit = iota
|
||||
// ANGLE_DEGREES_INT measures angles in degrees using integer arithmetic for speed. i.e. 0 <= angle < 360
|
||||
ANGLE_DEGREES_INT
|
||||
// ANGLE_DEGREES_FLOAT measures angles in degrees using floating point (slower). i.e. 0.0 <= angle < 360.0
|
||||
ANGLE_DEGREES_FLOAT
|
||||
// ANGLE_RADIANS measures angles in radians using floating point (slower). i.e. 0.0 <= angle < 2 * PI
|
||||
ANGLE_RADIANS
|
||||
)
|
||||
|
||||
const (
|
||||
// NATIVE_ANGLE_MAX is the maximum valid value for a native angle for a AS560x device
|
||||
NATIVE_ANGLE_MAX = (1 << 12) - 1 + iota
|
||||
// NATIVE_ANGLE_RANGE is the number of unique values for native angles for a AS560x device
|
||||
NATIVE_ANGLE_RANGE
|
||||
)
|
||||
|
||||
var (
|
||||
errRegisterNotFound = errors.New("Register not found")
|
||||
errMaxBurnAngle = errors.New("Max BURN_ANGLE limit reached")
|
||||
)
|
||||
|
||||
// BaseDevice handles the common behaviour between AS5600 & AS5601 devices
|
||||
type BaseDevice struct {
|
||||
bus drivers.I2C
|
||||
address uint8
|
||||
registers map[uint8]*i2cRegister
|
||||
maxAngle uint16
|
||||
}
|
||||
|
||||
// newBaseDevice creates a new base device given an I2C bus.
|
||||
func newBaseDevice(bus drivers.I2C) BaseDevice {
|
||||
// Add all 'base' registers, common to both AS5600 & AS5601
|
||||
conf := newI2CRegister(CONF, 0, 0x3fff, 2, reg_read|reg_write|reg_program)
|
||||
status := newI2CRegister(STATUS, 0, 0xff, 1, reg_read)
|
||||
regs := map[uint8]*i2cRegister{
|
||||
ZPOS: newI2CRegister(ZPOS, 0, 0xfff, 2, reg_read|reg_write|reg_program),
|
||||
CONF: conf,
|
||||
RAW_ANGLE: newI2CRegister(RAW_ANGLE, 0, 0xfff, 2, reg_read),
|
||||
ANGLE: newI2CRegister(ANGLE, 0, 0xfff, 2, reg_read),
|
||||
STATUS: status,
|
||||
AGC: newI2CRegister(AGC, 0, 0xff, 1, reg_read),
|
||||
MAGNITUDE: newI2CRegister(MAGNITUDE, 0, 0xfff, 2, reg_read),
|
||||
BURN: newI2CRegister(BURN, 0, 0xff, 1, reg_write),
|
||||
// Add common 'virtual registers' These are bitfields within the common registers above
|
||||
// A virtual register provides a convenient way to access the fields of a registers
|
||||
// by handling all of the necessary bitfield shifting and masking operations
|
||||
WD: newVirtualRegister(conf, 13, 0b1),
|
||||
FTH: newVirtualRegister(conf, 10, 0b111),
|
||||
SF: newVirtualRegister(conf, 8, 0b11),
|
||||
HYST: newVirtualRegister(conf, 2, 0b11),
|
||||
PM: newVirtualRegister(conf, 0, 0b11),
|
||||
MD: newVirtualRegister(status, 5, 0b1),
|
||||
ML: newVirtualRegister(status, 4, 0b1),
|
||||
MH: newVirtualRegister(status, 3, 0b1),
|
||||
}
|
||||
return BaseDevice{bus, DefaultAddress, regs, NATIVE_ANGLE_RANGE}
|
||||
}
|
||||
|
||||
// Configure sets up the AMS AS560x sensor device with the given configuration.
|
||||
func (d *BaseDevice) Configure(cfg Config) {
|
||||
if cfg.Address == 0 {
|
||||
cfg.Address = DefaultAddress
|
||||
}
|
||||
d.address = cfg.Address
|
||||
}
|
||||
|
||||
// ReadRegister reads the value for the given register from the AS560x device via I2C
|
||||
func (d *BaseDevice) ReadRegister(address uint8) (uint16, error) {
|
||||
reg, ok := d.registers[address]
|
||||
if !ok {
|
||||
return 0, errRegisterNotFound
|
||||
}
|
||||
return reg.read(d.bus, d.address)
|
||||
}
|
||||
|
||||
// WriteRegister writes the given value for the given register to the AS560x device via I2C
|
||||
func (d *BaseDevice) WriteRegister(address uint8, value uint16) error {
|
||||
reg, ok := d.registers[address]
|
||||
if !ok {
|
||||
return errRegisterNotFound
|
||||
}
|
||||
return reg.write(d.bus, d.address, value)
|
||||
}
|
||||
|
||||
// GetZeroPosition returns the 'zero position' (ZPOS) in various units
|
||||
func (d *BaseDevice) GetZeroPosition(units AngleUnit) (uint16, float32, error) {
|
||||
zpos, err := d.ReadRegister(ZPOS)
|
||||
if nil != err {
|
||||
return 0, 0.0, err
|
||||
}
|
||||
// Convert to requested units
|
||||
i, f := convertFromNativeAngle(zpos, NATIVE_ANGLE_RANGE, units)
|
||||
return i, f, nil
|
||||
}
|
||||
|
||||
// SetZeroPosition sets the 'zero position' (ZPOS) in various units
|
||||
func (d *BaseDevice) SetZeroPosition(zpos float32, units AngleUnit) error {
|
||||
return d.WriteRegister(ZPOS, convertToNativeAngle(zpos, units))
|
||||
}
|
||||
|
||||
// RawAngle reads the (unscaled & unadjusted) RAW_ANGLE register in various units
|
||||
func (d *BaseDevice) RawAngle(units AngleUnit) (uint16, float32, error) {
|
||||
angle, err := d.ReadRegister(RAW_ANGLE)
|
||||
if nil != err {
|
||||
return 0, 0.0, err
|
||||
}
|
||||
// Convert to requested units
|
||||
i, f := convertFromNativeAngle(angle, NATIVE_ANGLE_RANGE, units)
|
||||
return i, f, nil
|
||||
}
|
||||
|
||||
// Angle reads the (scaled & adjusted) ANGLE register in various units
|
||||
func (d *BaseDevice) Angle(units AngleUnit) (uint16, float32, error) {
|
||||
// ZPOS enables setting the 'zero position' of the device to any RAW_ANGLE value
|
||||
// ANGLE is RAW_ANGLE adjusted relative to ZPOS.
|
||||
angle, err := d.ReadRegister(ANGLE)
|
||||
if nil != err {
|
||||
return 0, 0.0, err
|
||||
}
|
||||
// Convert to requested units
|
||||
i, f := convertFromNativeAngle(angle, d.maxAngle, units)
|
||||
return i, f, nil
|
||||
}
|
||||
|
||||
// MagnetStatus reads the STATUS register and reports magnet position characteristics
|
||||
func (d *BaseDevice) MagnetStatus() (detected bool, strength MagnetStrength, err error) {
|
||||
status, err := d.ReadRegister(STATUS)
|
||||
if nil != err {
|
||||
return false, MagnetOk, err
|
||||
}
|
||||
detected = (status & STATUS_MD) != 0
|
||||
strength = MagnetOk
|
||||
if (status & STATUS_ML) != 0 {
|
||||
strength = MagnetTooWeak
|
||||
} else if (status & STATUS_MH) != 0 {
|
||||
strength = MagnetTooStrong
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Burn is a convenience method to program the device permanently by writing to the BURN register (limited number of times use!)
|
||||
func (d *BaseDevice) Burn(burnCmd BURN_CMD) error {
|
||||
if BURN_ANGLE == burnCmd {
|
||||
// BURN_ANGLE can only be executed up to 3 times.
|
||||
// We can check this in advance by reading ZMCO before writing to the BURN register.
|
||||
numBurns, err := d.ReadRegister(ZMCO)
|
||||
if nil != err {
|
||||
return err
|
||||
}
|
||||
if numBurns >= BURN_ANGLE_COUNT_MAX {
|
||||
// We're outta BURNs :(
|
||||
return errMaxBurnAngle
|
||||
}
|
||||
}
|
||||
return d.WriteRegister(BURN, uint16(burnCmd))
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package as560x // import tinygo.org/x/drivers/ams560x
|
||||
|
||||
import "math"
|
||||
|
||||
// convertFromNativeAngle converts and scales an angle from the device's native 12-bit range to the requested units
|
||||
func convertFromNativeAngle(angle uint16, maxAngle uint16, units AngleUnit) (uint16, float32) {
|
||||
// MANG == 0 & MANG == NATIVE_ANGLE_RANGE (1 << 12) mean the same thing: use full circle range
|
||||
// but the latter makes the maths/code simpler
|
||||
if 0 == maxAngle {
|
||||
maxAngle = NATIVE_ANGLE_RANGE
|
||||
}
|
||||
switch units {
|
||||
case ANGLE_NATIVE:
|
||||
// For native angles, scaling has already been done by the device
|
||||
return angle, float32(angle)
|
||||
case ANGLE_DEGREES_INT:
|
||||
// Convert to degrees using integer arithmetic. Less accuracy but faster
|
||||
var deg int = 0
|
||||
if NATIVE_ANGLE_RANGE == maxAngle {
|
||||
// Simplify the conversion when using the full range
|
||||
deg = int(angle) * 360 >> 12
|
||||
} else {
|
||||
// Using an integer degrees scale with a narrower native range is pointless since we don't
|
||||
// benefit at all from the increase in native resolution, in fact we LOSE precision.
|
||||
// Alas, we have to return something
|
||||
// First get maxAngle on the degrees scale
|
||||
degMang, _ := convertFromNativeAngle(maxAngle, NATIVE_ANGLE_RANGE, units)
|
||||
// Now scale angle
|
||||
deg = int(angle) * int(degMang) / NATIVE_ANGLE_RANGE
|
||||
}
|
||||
return uint16(deg), float32(deg)
|
||||
case ANGLE_DEGREES_FLOAT:
|
||||
// Convert to degrees using floating point. More accuracy at expense of speed
|
||||
var degF float32 = 0.0
|
||||
if NATIVE_ANGLE_RANGE == maxAngle {
|
||||
// Simplify the conversion when using the full range
|
||||
degF = float32(angle) * 360.0 / NATIVE_ANGLE_RANGE
|
||||
} else {
|
||||
// Scale to degrees using a narrower native range
|
||||
// First get maxAngle on the degrees scale
|
||||
_, degMangF := convertFromNativeAngle(maxAngle, NATIVE_ANGLE_RANGE, units)
|
||||
// Now scale angle
|
||||
degF = float32(angle) * degMangF / NATIVE_ANGLE_RANGE
|
||||
}
|
||||
return uint16(degF), degF
|
||||
case ANGLE_RADIANS:
|
||||
// Convert to radians. Can only be done using floating point.
|
||||
var rad float32 = 0.0
|
||||
if NATIVE_ANGLE_RANGE == maxAngle {
|
||||
// Simplify the conversion when using the full range
|
||||
rad = float32(angle) * 2 * math.Pi / NATIVE_ANGLE_RANGE
|
||||
} else {
|
||||
// Scale to radians using a narrower native range
|
||||
// First get maxAngle on the radians scale
|
||||
_, radMang := convertFromNativeAngle(maxAngle, NATIVE_ANGLE_RANGE, units)
|
||||
// Now scale angle
|
||||
rad = float32(angle) * radMang / NATIVE_ANGLE_RANGE
|
||||
}
|
||||
return uint16(rad), rad
|
||||
default:
|
||||
panic("Unknown angle measurement unit")
|
||||
}
|
||||
}
|
||||
|
||||
// convertToNativeAngle converts an angle from the requested units to the device's native 12-bit range.
|
||||
func convertToNativeAngle(angle float32, units AngleUnit) uint16 {
|
||||
var pos uint16 = 0
|
||||
switch units {
|
||||
case ANGLE_NATIVE:
|
||||
pos = uint16(angle)
|
||||
case ANGLE_DEGREES_INT:
|
||||
fallthrough
|
||||
case ANGLE_DEGREES_FLOAT:
|
||||
// Convert from degrees
|
||||
angle = float32(math.Mod(float64(angle), 360.0))
|
||||
if angle < 0.0 {
|
||||
angle += 360.0
|
||||
}
|
||||
pos = uint16(math.Round(float64(angle) * NATIVE_ANGLE_RANGE / 360.0))
|
||||
case ANGLE_RADIANS:
|
||||
// Convert from radians
|
||||
const circRad = 2.0 * math.Pi
|
||||
angle = float32(math.Mod(float64(angle), circRad))
|
||||
if angle < 0.0 {
|
||||
angle += circRad
|
||||
}
|
||||
pos = uint16(math.Round(float64(angle) * NATIVE_ANGLE_RANGE / circRad))
|
||||
default:
|
||||
panic("Unknown angle measurement unit")
|
||||
}
|
||||
if pos > NATIVE_ANGLE_MAX {
|
||||
pos = NATIVE_ANGLE_MAX
|
||||
}
|
||||
return pos
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package as560x // import tinygo.org/x/drivers/ams560x
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// registerAttributes is a bitfield of attributes for a register
|
||||
type registerAttributes uint8
|
||||
|
||||
const (
|
||||
// reg_read indicates that the register is readable
|
||||
reg_read registerAttributes = 1 << iota
|
||||
// reg_write indicates that the register is writeable
|
||||
reg_write
|
||||
// reg_program indicates that the register can be permanently programmed ('BURNed')
|
||||
reg_program
|
||||
)
|
||||
|
||||
var (
|
||||
errRegisterNotReadable = errors.New("Register is not readable")
|
||||
errRegisterNotWriteable = errors.New("Register is not writeable")
|
||||
)
|
||||
|
||||
// i2cRegister encapsulates the address, structure and read/write logic for a register on a AS560x device
|
||||
type i2cRegister struct {
|
||||
// host is the 'host register' for virtual registers. Physical/root registers have this set to self
|
||||
host *i2cRegister
|
||||
// address is the i2c address of the register. For 2-byte (word) addresses it's the low byte which holds the MSBs
|
||||
address uint8
|
||||
// shift is the number of bits the value is 'left shifted' into the register byte/word (0-15)
|
||||
shift uint16
|
||||
// mask is a bitwise mask applied to the register AFTER 'right shifting' to mask the register value
|
||||
mask uint16
|
||||
// num_bytes is the width of the register in bytes, 1 or 2.
|
||||
num_bytes uint8
|
||||
// attributes holds the register attributes. A bitfield of REG_xyz constants
|
||||
attributes registerAttributes
|
||||
// cached indicates whether we are holding a cached value of the register in value
|
||||
cached bool
|
||||
// value can be used as a 'cache' of the register's value for writeable registers.
|
||||
value uint16
|
||||
}
|
||||
|
||||
// newI2CRegister returns a pointer to a new i2cRegister with no cached value
|
||||
func newI2CRegister(address uint8, shift uint16, mask uint16, num_bytes uint8, attributes registerAttributes) *i2cRegister {
|
||||
reg := &i2cRegister{
|
||||
address: address,
|
||||
shift: shift,
|
||||
mask: mask,
|
||||
num_bytes: num_bytes,
|
||||
attributes: attributes,
|
||||
}
|
||||
// root registers host themselves
|
||||
reg.host = reg
|
||||
return reg
|
||||
}
|
||||
|
||||
// newVirtualRegister returns a pointer to a new i2cRegister with the given host register and shift/mask.
|
||||
func newVirtualRegister(host *i2cRegister, shift uint16, mask uint16) *i2cRegister {
|
||||
return &i2cRegister{
|
||||
host: host,
|
||||
address: host.address,
|
||||
shift: shift,
|
||||
mask: mask,
|
||||
num_bytes: host.num_bytes,
|
||||
attributes: host.attributes,
|
||||
}
|
||||
}
|
||||
|
||||
// invalidate invalidates any cached value for the register and forces an I2C read on the next read()
|
||||
func (r *i2cRegister) invalidate() {
|
||||
r.host.cached = false
|
||||
r.host.value = 0
|
||||
}
|
||||
|
||||
// readShiftAndMask is an internal method to read a value for the register over the given I2C bus from the device with the given address applying the given shift and mask
|
||||
func (r *i2cRegister) readShiftAndMask(bus drivers.I2C, deviceAddress uint8, shift uint16, mask uint16) (uint16, error) {
|
||||
if r.host.attributes®_read == 0 {
|
||||
return 0, errRegisterNotReadable
|
||||
}
|
||||
|
||||
// Only read over I2C if we don't have the host register value cached
|
||||
var val uint16 = r.host.value
|
||||
if !r.host.cached {
|
||||
// To avoid an alloc we always use an array of 2 bytes
|
||||
var buffer [2]byte
|
||||
var buf []byte
|
||||
if r.host.num_bytes < 2 {
|
||||
buf = buffer[:1]
|
||||
} else {
|
||||
buf = buffer[:]
|
||||
}
|
||||
// Read the host register over I2C
|
||||
err := legacy.ReadRegister(bus, deviceAddress, r.host.address, buf)
|
||||
if nil != err {
|
||||
return 0, err
|
||||
}
|
||||
// Unpack data from I2C
|
||||
if r.host.num_bytes > 1 {
|
||||
val = binary.BigEndian.Uint16(buf)
|
||||
} else {
|
||||
val = uint16(buf[0])
|
||||
}
|
||||
// cache this value if the host register is writeable. Note we cache the entire buffer without applying shift/mask
|
||||
if r.host.attributes®_write != 0 {
|
||||
r.host.value = val
|
||||
r.host.cached = true
|
||||
}
|
||||
}
|
||||
// Shift and mask the value before returning
|
||||
val >>= shift
|
||||
val &= mask
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// read reads a value for the register over the given I2C bus from the device with the given address.
|
||||
func (r *i2cRegister) read(bus drivers.I2C, deviceAddress uint8) (uint16, error) {
|
||||
return r.readShiftAndMask(bus, deviceAddress, r.shift, r.mask)
|
||||
}
|
||||
|
||||
// write writes a value for the register over the given I2C bus to the device with the given address.
|
||||
func (r *i2cRegister) write(bus drivers.I2C, deviceAddress uint8, value uint16) error {
|
||||
if r.host.attributes®_write == 0 {
|
||||
return errRegisterNotWriteable
|
||||
}
|
||||
var newValue uint16 = 0
|
||||
// Data sheet tells us to do a read first, modify only the desired bits and then write back
|
||||
// since (quote:) 'Blank fields may contain factory settings'
|
||||
// We will also need to do this anyway to support virtualRegister mappings on some registers
|
||||
// (e.g. CONF/STATUS)
|
||||
if (r.host.attributes & reg_read) > 0 { // not all registers are readable, e.g. BURN
|
||||
// read the host register's entire host byte/word, regardless of shift & mask
|
||||
readValue, error := r.readShiftAndMask(bus, deviceAddress, 0, 0xffff)
|
||||
if error != nil {
|
||||
return error
|
||||
}
|
||||
// Zero-out ONLY the relevant bits in newValue we just read
|
||||
readValue &= (0xffff ^ (r.mask << r.shift))
|
||||
newValue = readValue
|
||||
}
|
||||
// Mask the new value and shift it into place
|
||||
value &= r.mask
|
||||
value <<= r.shift
|
||||
// OR the masked & shifted value back into newValue to be written
|
||||
newValue |= value
|
||||
// Pack newValue into a byte buffer to write. To avoid an alloc we always use an array of 2 bytes
|
||||
var buffer [2]byte
|
||||
var buf []byte
|
||||
if r.host.num_bytes < 2 {
|
||||
buf = buffer[:1]
|
||||
buf[0] = uint8(newValue & 0xff)
|
||||
} else {
|
||||
buf = buffer[:]
|
||||
binary.BigEndian.PutUint16(buf, newValue)
|
||||
}
|
||||
|
||||
// Write the register from the buffer over I2C
|
||||
err := legacy.WriteRegister(bus, deviceAddress, r.host.address, buf)
|
||||
// after successful I2C write, cache this value if the host register (if also readable)
|
||||
// Note we cache the entire buffer without applying shift/mask
|
||||
if nil == err && r.host.attributes®_read != 0 {
|
||||
r.host.value = newValue
|
||||
r.host.cached = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
package as560x // import tinygo.org/x/drivers/ams560x
|
||||
|
||||
// DefaultAddress is the default I2C address of the AMS AS560x sensors (0x36).
|
||||
const DefaultAddress uint8 = 0x36
|
||||
|
||||
// AS560x common device registers
|
||||
const (
|
||||
// ZMCO contains the number of times a BURN_ANGLE command has been executed (max 3 burns)
|
||||
ZMCO = 0x00
|
||||
// ZPOS is the zero (start) position in RAW_ANGLE terms.
|
||||
ZPOS = 0x01
|
||||
// CONF supports custom config. Raw 14-bit register. See datasheet for mapping or use 'virtual registers' below.
|
||||
CONF = 0x07
|
||||
// STATUS indicates magnet position. Encapsulates MD, ML & MH. See also 'virtual registers' below.
|
||||
STATUS = 0x0b
|
||||
// RAW_ANGLE is the raw unscaled & unadjusted angle (12 bit: 0-4095/0xfff)
|
||||
RAW_ANGLE = 0x0c
|
||||
// ANGLE is RAW_ANGLE scaled & adjusted according to ZPOS (and MPOS/MANG on AS5600). (12 bit: 0-4095/0xfff)
|
||||
ANGLE = 0x0e
|
||||
// AGC is the Automatic Gain Control based on temp, airgap etc. 0-255 @ 5V, 0-128 @ 3.3V.
|
||||
AGC = 0x1a
|
||||
// MAGNITUDE indicates the magnitude value of the internal CORDIC output. See datasheet for more info.
|
||||
MAGNITUDE = 0x1b
|
||||
// BURN performs permanent programming of some registers. See BURN_XYZ cmd constants below for commands.
|
||||
BURN = 0xff
|
||||
)
|
||||
|
||||
// AS5600 specific registers
|
||||
const (
|
||||
// MPOS is the maximum position in RAW_ANGLE terms. With ZPOS, defines a 'narrower angle' for higher resolution.
|
||||
MPOS = 0x03
|
||||
// MANG is the maximum angle. With ZPOS, defines a 'narrower angle' for higher resolution.
|
||||
MANG = 0x05
|
||||
)
|
||||
|
||||
// AS5601 specific registers
|
||||
const (
|
||||
// ABN. See datasheet for mapping
|
||||
ABN = 0x09
|
||||
// PUSHTHR. Configures push-button function. See datasheet and AGC
|
||||
PUSHTHR = 0x0a
|
||||
)
|
||||
|
||||
// 'Virtual Registers' (VRs) are bitfields within the registers above.
|
||||
// These are not real register addresses recognized by the chip,
|
||||
// but they are recognized by the driver for convenience.
|
||||
|
||||
// virtualRegisterStartAddress defines the start of the virtual register address range.
|
||||
const virtualRegisterStartAddress = 0xa0
|
||||
|
||||
const (
|
||||
// VRs for CONF
|
||||
|
||||
// WD is a Virtual Register for the Watchdog timer. See WATCHDOG_TIMER consts.
|
||||
WD = iota + virtualRegisterStartAddress
|
||||
// FTH is a Virtual Register for the Fast Filter Threshold. See FAST_FILTER_THRESHOLD consts.
|
||||
FTH
|
||||
// SF is a Virtual Register for the Slow Filter. See SLOW_FILTER_RESPONSE consts.
|
||||
SF
|
||||
// PWMF is a Virtual Register for PWM Frequency (AS5600 ONLY). See PWM_FREQUENCY consts.
|
||||
PWMF
|
||||
// OUTS is a Virtual Register for the Output Stage (AS5600 ONLY). See OUTPUT_STAGE consts.
|
||||
OUTS
|
||||
// HYST is a Virtual Register for Hysteresis. See HYSTERESIS consts.
|
||||
HYST
|
||||
// PM is a Virtual Register for the Power Mode. See POWER_MODE consts.
|
||||
PM
|
||||
|
||||
// VRs for STATUS (0 = unset, 1 = set)
|
||||
|
||||
// MD is a Virtual Register for the 'Magnet was detected' flag.
|
||||
MD
|
||||
// ML is a Virtual Register for the 'AGC maximum gain overflow' a.k.a 'magnet too weak' flag.
|
||||
ML
|
||||
// MH is a Virtual Register for the 'AGC minimum gain overflow' a.k.a 'magnet too strong' flag.
|
||||
MH
|
||||
)
|
||||
|
||||
// POWER_MODE values for the PM component of CONF (and the PM VR)
|
||||
const (
|
||||
// PM_NOM is the normal 'always on' power mode. No polling, max 6.5mA current
|
||||
PM_NOM = iota
|
||||
// PM_LPM1 is Low Power Mode 1. 5ms polling, max 3.4mA current
|
||||
PM_LPM1
|
||||
// PM_LPM2 is Low Power Mode 2. 20ms polling, max 1.8mA current
|
||||
PM_LPM2
|
||||
// PM_LPM3 is Low Power Mode 3. 100ms polling, max 1.5mA current
|
||||
PM_LPM3
|
||||
)
|
||||
|
||||
// HYSTERESIS values for the HYST component of CONF (and the HYST VR)
|
||||
const (
|
||||
// HYST_OFF disables any hysteresis of the output
|
||||
HYST_OFF = iota
|
||||
// HYST_1LSB enables output hysteresis using 1 LSB
|
||||
HYST_1LSB
|
||||
// HYST_2LSB enables output hysteresis using 2 LSBs
|
||||
HYST_2LSB
|
||||
// HYST_3LSB enables output hysteresis using 3 LSBs
|
||||
HYST_3LSB
|
||||
)
|
||||
|
||||
// OUTPUT_STAGE values for the OUTS component of CONF (and the OUTS VR - AS5600 ONLY)
|
||||
const (
|
||||
// OS_ANALOG_FULL_RANGE enables analog output with full range (0%-100% VDD)
|
||||
OS_ANALOG_FULL_RANGE = iota
|
||||
// OS_ANALOG_REDUCED_RANGE enables analog output with reduced range (10%-90% VDD)
|
||||
OS_ANALOG_REDUCED_RANGE
|
||||
// OS_DIGITAL_PWM enables digital PWM output. Frequency determined by PWMF
|
||||
OS_DIGITAL_PWM
|
||||
)
|
||||
|
||||
// PWM_FREQUENCY values for the PWMF component of CONF (and the PWMF VR - ASS5600 ONLY)
|
||||
const (
|
||||
// PWMF_115_HZ enables PWM at 115 Hz
|
||||
PWMF_115_HZ = iota
|
||||
// PWMF_230_HZ enables PWM at 230 Hz
|
||||
PWMF_230_HZ
|
||||
// PWMF_460_HZ enables PWM at 460 Hz
|
||||
PWMF_460_HZ
|
||||
// PWMF_920_HZ enables PWM at 920 Hz
|
||||
PWMF_920_HZ
|
||||
)
|
||||
|
||||
// SLOW_FILTER_RESPONSE values for the SF (slow filter) component of CONF (and the SF VR)
|
||||
const (
|
||||
// SF_16X enables a 16x Slow Filter step response
|
||||
SF_16X = iota
|
||||
// SF_8X enables a 8x Slow Filter step response
|
||||
SF_8X
|
||||
// SF_4X enables a 4x Slow Filter step response
|
||||
SF_4X
|
||||
// SF_2X enables a 2x Slow Filter step response
|
||||
SF_2X
|
||||
)
|
||||
|
||||
// FAST_FILTER_THRESHOLD values for the FTH (fast filter threshold) component of CONF (and the FTH VR)
|
||||
const (
|
||||
// FTH_NONE disables the fast filter (slow filter only)
|
||||
FTH_NONE = iota
|
||||
// FTH_6LSB enables a fast filter threshold with 6 LSBs
|
||||
FTH_6LSB
|
||||
// FTH_7LSB enables a fast filter threshold with 7 LSBs
|
||||
FTH_7LSB
|
||||
// FTH_9LSB enables a fast filter threshold with 9 LSBs
|
||||
FTH_9LSB
|
||||
// FTH_18LSB enables a fast filter threshold with 18 LSBs
|
||||
FTH_18LSB
|
||||
// FTH_21LSB enables a fast filter threshold with 21 LSBs
|
||||
FTH_21LSB
|
||||
// FTH_24LSB enables a fast filter threshold with 24 LSBs
|
||||
FTH_24SB
|
||||
// FTH_10LSB enables a fast filter threshold with 10 LSBs
|
||||
FTH_10LSB
|
||||
)
|
||||
|
||||
// WATCHDOG_TIMER values for the WD component of CONF (and the WD VR)
|
||||
const (
|
||||
// WD_OFF disables the Watchdog Timer
|
||||
WD_OFF = iota
|
||||
// WD_ON enables the Watchdog Timer (automatic entry into LPM3 low-power mode enabled)
|
||||
WD_ON
|
||||
)
|
||||
|
||||
// constants for the raw STATUS register bitfield value.
|
||||
const (
|
||||
// STATUS_MH is set in STATUS when the magnet field is too strong (AGC minimum gain overflow)
|
||||
STATUS_MH = 1 << (iota + 3)
|
||||
// STATUS_ML is set in STATUS when the magnet field is too weak (AGC maximum gain overflow)
|
||||
STATUS_ML
|
||||
// STATUS_MD is set n STATUS when the magnet is detected. Doesn't seem to work with some units.
|
||||
STATUS_MD
|
||||
)
|
||||
|
||||
// ABN_MAPPING values for the ABN register (AS5601 ONLY)
|
||||
const (
|
||||
// ABN_8 configures 8 output positions (61 Hz)
|
||||
ABN_8 = iota
|
||||
// ABN_16 configures 16 output positions (122 Hz)
|
||||
ABN_16
|
||||
// ABN_32 configures 32 output positions (244 Hz)
|
||||
ABN_32
|
||||
// ABN_64 configures 64 output positions (488 Hz)
|
||||
ABN_64
|
||||
// ABN_128 configures 128 output positions (976 Hz)
|
||||
ABN_128
|
||||
// ABN_256 configures 256 output positions (1.95 KHz)
|
||||
ABN_256
|
||||
// ABN_512 configures 512 output positions (3.9 KHz)
|
||||
ABN_512
|
||||
// ABN_1024 configures 1024 output positions (7.8 KHz)
|
||||
ABN_1024
|
||||
// ABN_2048 configures 2048 output positions (15.6 KHz)
|
||||
ABN_2048
|
||||
)
|
||||
|
||||
// BURN_CMD is a command to write to the BURN register.
|
||||
type BURN_CMD uint16
|
||||
|
||||
const (
|
||||
// BURN_ANGLE is the value to write to BURN to permanently program ZPOS & MPOS (Max 3 times!)
|
||||
BURN_ANGLE BURN_CMD = 0x80
|
||||
// BURN_SETTING is the value to write to BURN to permanently program MANG & CONF (ONCE ONLY!)
|
||||
BURN_SETTING BURN_CMD = 0x40
|
||||
)
|
||||
|
||||
// BURN_ANGLE_COUNT_MAX is a constant for the maximum number of times a BURN_ANGLE command can be executed. Compare this with ZMCO
|
||||
const BURN_ANGLE_COUNT_MAX uint16 = 3
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a DS3231 device.
|
||||
// Device wraps an I2C connection to an AT24CX device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
// Package axp192 provides a driver for the axp192 I2C Enhanced single Cell
|
||||
// Li-Battery and Power System Management IC.
|
||||
//
|
||||
// http://www.x-powers.com/en.php/Info/product_detail/article_id/29
|
||||
// Datasheet: https://github.com/m5stack/M5-Schematic/blob/master/Core/AXP192%20Datasheet_v1.1_en_draft_2211.pdf
|
||||
package axp192 // import "tinygo.org/x/drivers/axp192"
|
||||
|
||||
import (
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
type Error uint8
|
||||
|
||||
const (
|
||||
ErrInvalidID Error = 0x1
|
||||
)
|
||||
|
||||
func (e Error) Error() string {
|
||||
switch e {
|
||||
case ErrInvalidID:
|
||||
return "Invalid chip ID"
|
||||
default:
|
||||
return "Unknown error"
|
||||
}
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
buf []byte
|
||||
Address uint8
|
||||
}
|
||||
|
||||
// New returns AXP192 device for the provided I2C bus using default address.
|
||||
func New(i2c drivers.I2C) *Device {
|
||||
return &Device{
|
||||
bus: i2c,
|
||||
buf: make([]byte, 2),
|
||||
Address: Address,
|
||||
}
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
}
|
||||
|
||||
// Configure the AXP192 device.
|
||||
func (d *Device) Configure(config Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadPowerSupplyStatus reads power supply status.
|
||||
func (d *Device) ReadPowerSupplyStatus() uint8 {
|
||||
return d.read8bit(RegPowerSupplyStatus)
|
||||
}
|
||||
|
||||
// SetVbusIPSOutAccessManagement sets VBUS-IPSOUT access management.
|
||||
func (d *Device) SetVbusIPSOutAccessManagement(a uint8) {
|
||||
d.write1Byte(RegVbusIPSOutAccessManagement, a)
|
||||
}
|
||||
|
||||
// GetVbusIPSOutAccessManagement gets VBUS-IPSOUT access management.
|
||||
func (d *Device) GetVbusIPSOutAccessManagement() uint8 {
|
||||
return d.read8bit(RegVbusIPSOutAccessManagement)
|
||||
}
|
||||
|
||||
// SetGPIO1Control sets GPIO1 function.
|
||||
func (d *Device) SetGPIO1Control(a uint8) {
|
||||
d.write1Byte(RegGPIO1Control, a)
|
||||
}
|
||||
|
||||
// GetGPIO1Control gets GPIO1 function.
|
||||
func (d *Device) GetGPIO1Control() uint8 {
|
||||
return d.read8bit(RegGPIO1Control)
|
||||
}
|
||||
|
||||
// SetGPIO2Control sets GPIO2 function.
|
||||
func (d *Device) SetGPIO2Control(a uint8) {
|
||||
d.write1Byte(RegGPIO2Control, a)
|
||||
}
|
||||
|
||||
// GetGPIO2Control gets GPIO2 function.
|
||||
func (d *Device) GetGPIO2Control() uint8 {
|
||||
return d.read8bit(RegGPIO2Control)
|
||||
}
|
||||
|
||||
// SetGPIO20SignalStatus sets GPIO[2:0] signal status.
|
||||
func (d *Device) SetGPIO20SignalStatus(a uint8) {
|
||||
d.write1Byte(RegGPIO20SignalStatus, a)
|
||||
}
|
||||
|
||||
// GetGPIO20SignalStatus gets GPIO[2:0] signal status.
|
||||
func (d *Device) GetGPIO20SignalStatus() uint8 {
|
||||
return d.read8bit(RegGPIO20SignalStatus)
|
||||
}
|
||||
|
||||
// SetBackupBatteryChargingControl sets backup battery charge control.
|
||||
func (d *Device) SetBackupBatteryChargingControl(a uint8) {
|
||||
d.write1Byte(RegBackupBatteryChargingControl, a)
|
||||
}
|
||||
|
||||
// GetBackupBatteryChargingControl gets backup battery charge control.
|
||||
func (d *Device) GetBackupBatteryChargingControl() uint8 {
|
||||
return d.read8bit(RegBackupBatteryChargingControl)
|
||||
}
|
||||
|
||||
// SetDCDC1VoltageSet sets DC-DC1 output voltage.
|
||||
func (d *Device) SetDCDC1VoltageSet(a uint8) {
|
||||
d.write1Byte(RegDCDC1VoltageSet, a)
|
||||
}
|
||||
|
||||
// GetDCDC1VoltageSet gets DC-DC1 output voltage.
|
||||
func (d *Device) GetDCDC1VoltageSet() uint8 {
|
||||
return d.read8bit(RegDCDC1VoltageSet)
|
||||
}
|
||||
|
||||
// SetDCDC2VoltageSet sets DC-DC2 dynamic voltage parameter.
|
||||
func (d *Device) SetDCDC2VoltageSet(a uint8) {
|
||||
d.write1Byte(RegDCDC2VoltageSet, a)
|
||||
}
|
||||
|
||||
// GetDCDC2VoltageSet gets DC-DC2 dynamic voltage parameter.
|
||||
func (d *Device) GetDCDC2VoltageSet() uint8 {
|
||||
return d.read8bit(RegDCDC2VoltageSet)
|
||||
}
|
||||
|
||||
// SetDCDC3VoltageSet sets DC-DC3 output voltage.
|
||||
func (d *Device) SetDCDC3VoltageSet(a uint8) {
|
||||
d.write1Byte(RegDCDC3VoltageSet, a)
|
||||
}
|
||||
|
||||
// GetDCDC3VoltageSet gets DC-DC3 output voltage.
|
||||
func (d *Device) GetDCDC3VoltageSet() uint8 {
|
||||
return d.read8bit(RegDCDC3VoltageSet)
|
||||
}
|
||||
|
||||
// SetLDO23VoltageSet sets LDO2/3 output voltage.
|
||||
func (d *Device) SetLDO23VoltageSet(a uint8) {
|
||||
d.write1Byte(RegLDO23VoltageSet, a)
|
||||
}
|
||||
|
||||
// GetLDO23VoltageSet gets LDO2/3 output voltage.
|
||||
func (d *Device) GetLDO23VoltageSet() uint8 {
|
||||
return d.read8bit(RegLDO23VoltageSet)
|
||||
}
|
||||
|
||||
// SetDCDC13LDO23Switch sets DC-DC1/3 & LOD2/3 output control.
|
||||
func (d *Device) SetDCDC13LDO23Switch(a uint8) {
|
||||
d.write1Byte(RegDCDC13LDO23Switch, a)
|
||||
}
|
||||
|
||||
// GetDCDC13LDO23Switch gets DC-DC1/3 & LOD2/3 output control.
|
||||
func (d *Device) GetDCDC13LDO23Switch() uint8 {
|
||||
return d.read8bit(RegDCDC13LDO23Switch)
|
||||
}
|
||||
|
||||
// SetGPIO43FunctionControl sets GPIO[4:3] pin function.
|
||||
func (d *Device) SetGPIO43FunctionControl(a uint8) {
|
||||
d.write1Byte(RegGPIO43FunctionControl, a)
|
||||
}
|
||||
|
||||
// GetGPIO43FunctionControl gets GPIO[4:3] pin function.
|
||||
func (d *Device) GetGPIO43FunctionControl() uint8 {
|
||||
return d.read8bit(RegGPIO43FunctionControl)
|
||||
}
|
||||
|
||||
// SetPEKParameterSet sets PEK press key parameter.
|
||||
func (d *Device) SetPEKParameterSet(a uint8) {
|
||||
d.write1Byte(RegPEKParameterSet, a)
|
||||
}
|
||||
|
||||
// GetPEKParameterSet gets PEK press key parameter.
|
||||
func (d *Device) GetPEKParameterSet() uint8 {
|
||||
return d.read8bit(RegPEKParameterSet)
|
||||
}
|
||||
|
||||
// SetADCEnableSet sets ADC enable 1.
|
||||
func (d *Device) SetADCEnableSet(a uint8) {
|
||||
d.write1Byte(RegADCEnableSet, a)
|
||||
}
|
||||
|
||||
// GetADCEnableSet gets ADC enable 1.
|
||||
func (d *Device) GetADCEnableSet() uint8 {
|
||||
return d.read8bit(RegADCEnableSet)
|
||||
}
|
||||
|
||||
// SetGPIO43SignalStatus sets GPIO[4:3] signal status.
|
||||
func (d *Device) SetGPIO43SignalStatus(a uint8) {
|
||||
d.write1Byte(RegGPIO43SignalStatus, a)
|
||||
}
|
||||
|
||||
// GetGPIO43SignalStatus gets GPIO[4:3] signal status.
|
||||
func (d *Device) GetGPIO43SignalStatus() uint8 {
|
||||
return d.read8bit(RegGPIO43SignalStatus)
|
||||
}
|
||||
|
||||
// SetDCVoltage sets DC voltage.
|
||||
func (d *Device) SetDCVoltage(number uint8, voltage uint16) {
|
||||
if voltage < 700 {
|
||||
voltage = 0
|
||||
} else {
|
||||
voltage = (voltage - 700) / 25
|
||||
}
|
||||
|
||||
switch number {
|
||||
case 0:
|
||||
v := d.GetDCDC1VoltageSet()
|
||||
d.SetDCDC1VoltageSet((v & 0x80) | (uint8(voltage) & 0x7F))
|
||||
case 1:
|
||||
v := d.GetDCDC2VoltageSet()
|
||||
d.SetDCDC2VoltageSet((v & 0x80) | (uint8(voltage) & 0x7F))
|
||||
case 2:
|
||||
v := d.GetDCDC3VoltageSet()
|
||||
d.SetDCDC3VoltageSet((v & 0x80) | (uint8(voltage) & 0x7F))
|
||||
}
|
||||
}
|
||||
|
||||
// SetLDOVoltage sets LDO voltage.
|
||||
func (d *Device) SetLDOVoltage(number uint8, voltage uint16) {
|
||||
if voltage > 3300 {
|
||||
voltage = 15
|
||||
} else {
|
||||
voltage = (voltage / 100) - 18
|
||||
}
|
||||
|
||||
switch number {
|
||||
case 2:
|
||||
v := d.GetLDO23VoltageSet()
|
||||
d.SetLDO23VoltageSet((v & 0x0F) | (uint8(voltage) << 4))
|
||||
break
|
||||
case 3:
|
||||
v := d.GetLDO23VoltageSet()
|
||||
d.SetLDO23VoltageSet((v & 0xF0) | uint8(voltage))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// SetLDOEnable enable LDO.
|
||||
func (d *Device) SetLDOEnable(number uint8, state bool) {
|
||||
mark := uint8(0x01)
|
||||
mark <<= number
|
||||
switch number {
|
||||
case 2:
|
||||
v := d.GetDCDC13LDO23Switch()
|
||||
d.SetDCDC13LDO23Switch(v | mark)
|
||||
case 3:
|
||||
v := d.GetDCDC13LDO23Switch()
|
||||
d.SetDCDC13LDO23Switch(v & (^mark))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) write1Byte(reg, data uint8) {
|
||||
legacy.WriteRegister(d.bus, d.Address, reg, []byte{data})
|
||||
}
|
||||
|
||||
func (d *Device) read8bit(reg uint8) uint8 {
|
||||
legacy.ReadRegister(d.bus, d.Address, reg, d.buf[:1])
|
||||
return d.buf[0]
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package axp192
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
axp192orig "tinygo.org/x/drivers/axp192"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a AXP192 device.
|
||||
type Device struct {
|
||||
*axp192orig.Device
|
||||
LED Pin
|
||||
RST Pin
|
||||
SPK_EN Pin
|
||||
}
|
||||
|
||||
// New creates a new AXP192 connection. The I2C bus must already be
|
||||
// configured.
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(i2c drivers.I2C) *Device {
|
||||
d := axp192orig.New(i2c)
|
||||
|
||||
axp := &Device{
|
||||
Device: d,
|
||||
}
|
||||
axp.LED = Pin{pin: 1, axp: axp}
|
||||
axp.SPK_EN = Pin{pin: 2, axp: axp}
|
||||
axp.RST = Pin{pin: 4, axp: axp}
|
||||
|
||||
axp.begin()
|
||||
|
||||
return axp
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication
|
||||
func (d *Device) Configure(config Config) error {
|
||||
return d.Device.Configure(axp192orig.Config{})
|
||||
}
|
||||
|
||||
func (d *Device) begin() {
|
||||
d.SetVbusIPSOutAccessManagement((d.GetVbusIPSOutAccessManagement() & 0x04) | 0x02)
|
||||
d.SetGPIO1Control(d.GetGPIO1Control() & 0xF8)
|
||||
d.SetGPIO2Control(d.GetGPIO2Control() & 0xF8)
|
||||
d.SetBackupBatteryChargingControl((d.GetBackupBatteryChargingControl() & 0x1C) | 0xA2)
|
||||
d.SetESPVoltage(3350)
|
||||
d.SetLcdVoltage(3300)
|
||||
d.SetLDOVoltage(2, 3300) //Periph power voltage preset (LCD_logic, SD card)
|
||||
d.SetLDOVoltage(3, 2000) //Vibrator power voltage preset
|
||||
|
||||
d.SetLDOEnable(2, true)
|
||||
d.SetDCDC3(true) // LCD Backlight
|
||||
// GPIO4 : LCD Reset
|
||||
d.SetGPIO43FunctionControl((d.GetGPIO43FunctionControl() & 0x72) | 0x84)
|
||||
// Power On/Off Setting
|
||||
d.SetPEKParameterSet(0x4C)
|
||||
d.SetADCEnableSet(0xFF)
|
||||
|
||||
d.RST.Low()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.RST.High()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// ToggleLED toggles LED connected to AXP192.
|
||||
func (d *Device) ToggleLED() {
|
||||
v := d.GetGPIO20SignalStatus()
|
||||
if (v & 0x02) > 0 {
|
||||
d.SetGPIO20SignalStatus(v & 0xFD)
|
||||
} else {
|
||||
d.SetGPIO20SignalStatus(v | 0x02)
|
||||
}
|
||||
}
|
||||
|
||||
// SetESPVoltage sets voltage of ESP32.
|
||||
func (d *Device) SetESPVoltage(voltage uint16) {
|
||||
if voltage >= 3000 && voltage <= 3400 {
|
||||
d.SetDCVoltage(0, voltage)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLcdVoltage sets voltage of LCD.
|
||||
func (d *Device) SetLcdVoltage(voltage uint16) {
|
||||
if voltage >= 2500 && voltage <= 3300 {
|
||||
d.SetDCVoltage(2, voltage)
|
||||
}
|
||||
}
|
||||
|
||||
// SetDCDC3 enables or disables DCDC3.
|
||||
func (d *Device) SetDCDC3(State bool) {
|
||||
v := d.GetDCDC13LDO23Switch()
|
||||
if State == true {
|
||||
v = (1 << 1) | v
|
||||
} else {
|
||||
v = ^(uint8(1) << 1) & v
|
||||
}
|
||||
d.SetDCDC13LDO23Switch(v)
|
||||
}
|
||||
|
||||
// Pin is a single pin on AXP192.
|
||||
type Pin struct {
|
||||
pin uint8
|
||||
axp *Device
|
||||
}
|
||||
|
||||
// High sets this GPIO pin to high.
|
||||
func (p Pin) High() {
|
||||
switch p.pin {
|
||||
case 1: // LED
|
||||
v := p.axp.GetGPIO20SignalStatus()
|
||||
p.axp.SetGPIO20SignalStatus(v | 0x02)
|
||||
case 2: // SPK_EN
|
||||
case 4: // RST
|
||||
v := p.axp.GetGPIO43SignalStatus()
|
||||
v |= uint8(0x02)
|
||||
p.axp.SetGPIO43SignalStatus(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Low sets this GPIO pin to low.
|
||||
func (p Pin) Low() {
|
||||
switch p.pin {
|
||||
case 1: // LED
|
||||
v := p.axp.GetGPIO20SignalStatus()
|
||||
p.axp.SetGPIO20SignalStatus(v & 0xFD)
|
||||
case 2: // SPK_EN
|
||||
case 4: // RST
|
||||
v := p.axp.GetGPIO43SignalStatus()
|
||||
v &= ^uint8(0x02)
|
||||
p.axp.SetGPIO43SignalStatus(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle switches an output pin from low to high or from high to low.
|
||||
func (p Pin) Toggle() {
|
||||
switch p.pin {
|
||||
case 1: // LED
|
||||
v := p.axp.GetGPIO20SignalStatus()
|
||||
if (v & 0x02) == 0 {
|
||||
p.axp.SetGPIO20SignalStatus(v | 0x02)
|
||||
} else {
|
||||
p.axp.SetGPIO20SignalStatus(v & 0xFD)
|
||||
}
|
||||
case 2: // SPK_EN
|
||||
case 4: // RST
|
||||
v := p.axp.GetGPIO43SignalStatus()
|
||||
if (v & 0x02) == 0 {
|
||||
v |= uint8(0x02)
|
||||
} else {
|
||||
v &= ^uint8(0x02)
|
||||
}
|
||||
p.axp.SetGPIO43SignalStatus(v)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package axp192
|
||||
|
||||
// power supply control class
|
||||
// 0x00 Power supply status register
|
||||
// 0x01 Power supply mode/charging status register
|
||||
// 0x04 OTG VBUS status register
|
||||
// 0x06‐09 Data buffer register
|
||||
// 0x10 EXTEN & DC‐DC2 switch register
|
||||
// 0x12 DC‐DC1/3 & LDO2/3switch register
|
||||
// 0x23 DC‐DC2 voltage set register
|
||||
// 0x25 DC‐DC2 voltage slope set register
|
||||
// 0x26 DC‐DC1voltage set register
|
||||
// 0x27 DC‐DC3 voltage set register
|
||||
// 0x28 LDO2/3 voltage set register
|
||||
// 0x30 VBUS‐IPSOUT access set register
|
||||
// 0x31 VOFF power off voltage set register
|
||||
// 0x32 Power off、battery detect、CHGLED control register
|
||||
// 0x33 Charging control register1
|
||||
// 0x34 Charging control register2
|
||||
// 0x35 Backup battery charging control register
|
||||
// 0x36 PEK parameter set register
|
||||
// 0x37 DCDC switch frequency set register
|
||||
// 0x38 Battery charging under temperature warning set register
|
||||
// 0x39 Battery charging over temperature warning set register
|
||||
// 0x3A APS under voltage Level1 set register
|
||||
// 0x3B APS under voltage Level2 set register
|
||||
// 0x3C Battery discharging under temperature warning set register
|
||||
// 0x3D Battery discharging over temperature warning set register
|
||||
// 0x80 DCDC mode set register
|
||||
// 0x82 ADC enable set register 1
|
||||
// 0x83 ADC enable set register 2
|
||||
// 0x84 ADC sample frequency set, TS pin control register
|
||||
// 0x85 GPIO [3:0] input range set register
|
||||
// 0x8A Timer control register
|
||||
// 0x8B VBUS monitor set register
|
||||
// 0x8F Over temperature power off control register
|
||||
|
||||
// GPIO control class
|
||||
// 0x90 GPIO0 control register
|
||||
// 0x91 GPIO0 LDO mode output voltage set register
|
||||
// 0x92 GPIO1 control register
|
||||
// 0x93 GPIO2 control register
|
||||
// 0x94 GPIO[2:0] signal status register
|
||||
// 0x95 GPIO[4:3] function control register
|
||||
// 0x96 GPIO[4:3] signal status register
|
||||
// 0x97 GPIO[2:0] pull down control register
|
||||
// 0x98 PWM1 frequency set register
|
||||
// 0x99 PWM1 duty ratio set register 1
|
||||
// 0x9A PWM1 duty ratio set register 2
|
||||
// 0x9B PWM2 frequency set register
|
||||
// 0x9C PWM2 duty ratio set register 1
|
||||
// 0x9D PWM2 duty ratio set register 2
|
||||
// 0x9E GPIO5 control register
|
||||
|
||||
// IRQ control class
|
||||
// 0x40 IRQ enable control register 1
|
||||
// 0x41 IRQ enable control register 2
|
||||
// 0x42 IRQ enable control register 3
|
||||
// 0x43 IRQ enable control register 4
|
||||
// 0x44 IRQ status register 1
|
||||
// 0x45 IRQ status register 2
|
||||
// 0x46 IRQ status register 3
|
||||
// 0x47 IRQ status register 4
|
||||
|
||||
// ADC data class
|
||||
// 0x56 ACIN voltage ADC data high 8 bit
|
||||
// 0x57 ACIN voltage ADC data low 4 bit
|
||||
// 0x58 ACIN current ADC data high 8 bit
|
||||
// 0x59 ACIN current ADC data low 4 bit
|
||||
// 0x5A VBUS voltage ADC data high 8 bit
|
||||
// 0x5B VBUS voltage ADC data low 4 bit
|
||||
// 0x5C VBUS current ADC data high 8 bit
|
||||
// 0x5D VBUS current ADC data low 4 bit
|
||||
// 0x5E AXP192 internal temperature monitor ADC data High 8 bit
|
||||
// 0x5F AXP192 internal temperature monitor ADC data low 4 bit
|
||||
// 0x62 TS input ADC data High 8 bit,monitor battery temperature by default
|
||||
// 0x63 TS input ADC data low 4 bit,monitor battery temperature by default
|
||||
// 0x64 GPIO0 voltage ADC data high 8 bit
|
||||
// 0x65 GPIO0 voltage ADC data low 4 bit
|
||||
// 0x66 GPIO1 voltage ADC data high 8 bit
|
||||
// 0x67 GPIO1 voltage ADC data low 4 bit
|
||||
// 0x68 GPIO2 voltage ADC data high 8 bit
|
||||
// 0x69 GPIO2 voltage ADC data low 4 bit
|
||||
// 0x6A GPIO[3] voltage ADC data high 8 bit
|
||||
// 0x6B GPIO[3] voltage ADC data low 4 bit
|
||||
// 0x70 Battery instantaneous power high 8 bit
|
||||
// 0x71 Battery instantaneous power middle 8 bit
|
||||
// 0x72 Battery instantaneous power low 8 bit
|
||||
// 0x78 Battery voltage high 8 bit
|
||||
// 0x79 Battery voltage low 4 bit
|
||||
// 0x7A Battery charging current high 8 bit
|
||||
// 0x7B Battery charging current low 5 bit
|
||||
// 0x7C Battery discharging current high 8 bit
|
||||
// 0x7D Battery discharging current low 5 bit
|
||||
// 0x7E APS voltage high 8 bit
|
||||
// 0x7F APS voltage low 4 bit
|
||||
// 0xB0 Battery charging coulomb counter data register 3
|
||||
// 0xB1 Battery charging coulomb counter data register 2
|
||||
// 0xB2 Battery charging coulomb counter data register 1
|
||||
// 0xB3 Battery charging coulomb counter data register 0
|
||||
// 0xB4 Battery discharging coulomb counter data register 3
|
||||
// 0xB5 Battery discharging coulomb counter data register 2
|
||||
// 0xB6 Battery discharging coulomb counter data register 1
|
||||
// 0xB7 Battery discharging coulomb counter data register 0
|
||||
// 0xB8 Coulomb counter control register
|
||||
|
||||
const (
|
||||
// Address is default I2C address.
|
||||
Address = 0x34
|
||||
|
||||
RegPowerSupplyStatus = 0x00
|
||||
RegDCDC13LDO23Switch = 0x12
|
||||
RegVbusIPSOutAccessManagement = 0x30
|
||||
RegBackupBatteryChargingControl = 0x35
|
||||
RegDCDC2VoltageSet = 0x25
|
||||
RegDCDC1VoltageSet = 0x26
|
||||
RegDCDC3VoltageSet = 0x27
|
||||
RegLDO23VoltageSet = 0x28
|
||||
RegPEKParameterSet = 0x36
|
||||
RegADCEnableSet = 0x82
|
||||
|
||||
RegGPIO1Control = 0x92
|
||||
RegGPIO2Control = 0x93
|
||||
RegGPIO20SignalStatus = 0x94
|
||||
RegGPIO43FunctionControl = 0x95
|
||||
RegGPIO43SignalStatus = 0x96
|
||||
)
|
||||
@@ -2,7 +2,6 @@
|
||||
//
|
||||
// Datasheet:
|
||||
// https://www.mouser.com/ds/2/348/bh1750fvi-e-186247.pdf
|
||||
//
|
||||
package bh1750 // import "tinygo.org/x/drivers/bh1750"
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Package blinkm implements a driver for the BlinkM I2C RGB LED.
|
||||
//
|
||||
// Datasheet: http://thingm.com/fileadmin/thingm/downloads/BlinkM_datasheet.pdf
|
||||
//
|
||||
package blinkm // import "tinygo.org/x/drivers/blinkm"
|
||||
|
||||
import "tinygo.org/x/drivers"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,352 @@
|
||||
// Package bma42x provides a driver for the BMA421 and BMA425 accelerometer
|
||||
// chips.
|
||||
//
|
||||
// Here is a reasonably good datasheet:
|
||||
// https://datasheet.lcsc.com/lcsc/1912111437_Bosch-Sensortec-BMA425_C437656.pdf
|
||||
//
|
||||
// This driver was originally written for the PineTime, using the datasheet as a
|
||||
// guide. There is an open source C driver provided by Bosch, but unfortunately
|
||||
// it needs some small modifications to work with other chips (most importantly,
|
||||
// the "config file").
|
||||
// The InfiniTime and Wasp-OS drivers for this accelerometer have also been used
|
||||
// to figure out some driver details (especially step counting).
|
||||
package bma42x
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"errors"
|
||||
"reflect"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
// Driver for BMA421 and BMA425:
|
||||
// BMA421: https://files.pine64.org/doc/datasheet/pinetime/BST-BMA421-FL000.pdf
|
||||
// BMA425: https://datasheet.lcsc.com/lcsc/1912111437_Bosch-Sensortec-BMA425_C437656.pdf
|
||||
|
||||
// This is the BMA421 firmware from the Wasp-OS project.
|
||||
// It is identical to the so-called BMA423 firmware in InfiniTime, which I
|
||||
// suspect to be actually a BMA421 firmware. I don't know where this firmware
|
||||
// comes from or what the licensing status is.
|
||||
// It has the FEATURES_IN command prepended, so that it can be written directly
|
||||
// using I2C.Tx.
|
||||
// Source: https://github.com/wasp-os/bma42x-upy/blob/master/BMA42X-Sensor-API/bma421.h
|
||||
//
|
||||
//go:embed bma421-config-waspos.bin
|
||||
var bma421Firmware string
|
||||
|
||||
// Same as the BMA421 firmware, but for the BMA425.
|
||||
// Source: https://github.com/wasp-os/bma42x-upy/blob/master/BMA42X-Sensor-API/bma425.h
|
||||
//
|
||||
//go:embed bma425-config-waspos.bin
|
||||
var bma425Firmware string
|
||||
|
||||
var (
|
||||
errUnknownDevice = errors.New("bma42x: unknown device")
|
||||
errUnsupportedDevice = errors.New("bma42x: device not part of config")
|
||||
errConfigMismatch = errors.New("bma42x: config mismatch")
|
||||
errTimeout = errors.New("bma42x: timeout")
|
||||
errInitFailed = errors.New("bma42x: failed to initialize")
|
||||
)
|
||||
|
||||
const Address = 0x18 // BMA421/BMA425 address
|
||||
|
||||
type DeviceType uint8
|
||||
|
||||
const (
|
||||
DeviceBMA421 DeviceType = 1 << iota
|
||||
DeviceBMA425
|
||||
|
||||
AnyDevice = DeviceBMA421 | DeviceBMA425
|
||||
noDevice DeviceType = 0
|
||||
)
|
||||
|
||||
// Features to enable while configuring the accelerometer.
|
||||
type Features uint8
|
||||
|
||||
const (
|
||||
FeatureStepCounting = 1 << iota
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Which devices to support (OR the device types together as needed).
|
||||
Device DeviceType
|
||||
|
||||
// Which features to enable. With Features == 0, only the accelerometer will
|
||||
// be enabled.
|
||||
Features Features
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
address uint8
|
||||
accelData [6]byte
|
||||
combinedTempSteps [5]uint8 // [0:3] steps, [4] temperature
|
||||
dataBuf [2]byte
|
||||
}
|
||||
|
||||
func NewI2C(i2c drivers.I2C, address uint8) *Device {
|
||||
return &Device{
|
||||
bus: i2c,
|
||||
address: address,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) Connected() bool {
|
||||
val, err := d.read1(_CHIP_ID)
|
||||
return err == nil && identifyChip(val) != noDevice
|
||||
}
|
||||
|
||||
func (d *Device) Configure(config Config) error {
|
||||
if config.Device == 0 {
|
||||
config.Device = AnyDevice
|
||||
}
|
||||
|
||||
// Check chip ID, to check the connection and to determine which BMA42x
|
||||
// device we're dealing with.
|
||||
chipID, err := d.read1(_CHIP_ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine which firmware (config file?) we'll be using.
|
||||
// There is an extra check for the device before using the given firmware.
|
||||
// This check will typically be optimized away if the given device is not
|
||||
// configured, so that the firmware (which is 6kB in size!) won't be linked
|
||||
// into the binary.
|
||||
var firmware string
|
||||
switch identifyChip(chipID) {
|
||||
case DeviceBMA421:
|
||||
if config.Device&DeviceBMA421 == 0 {
|
||||
return errUnsupportedDevice
|
||||
}
|
||||
firmware = bma421Firmware
|
||||
case DeviceBMA425:
|
||||
if config.Device&DeviceBMA425 == 0 {
|
||||
return errUnsupportedDevice
|
||||
}
|
||||
firmware = bma425Firmware
|
||||
default:
|
||||
return errUnknownDevice
|
||||
}
|
||||
|
||||
// Reset the chip, to be able to initialize it properly.
|
||||
// The datasheet says a delay is needed after a SoftReset, but it doesn't
|
||||
// say how long this delay should be. The bma423 driver however uses a 200ms
|
||||
// delay, so that's what we'll be using.
|
||||
err = d.write1(_CMD, cmdSoftReset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// Disable power saving.
|
||||
err = d.write1(_PWR_CONF, 0x00)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(450 * time.Microsecond)
|
||||
|
||||
// Start initialization (because the datasheet says so).
|
||||
err = d.write1(_INIT_CTRL, 0x00)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write "config file" (actually a firmware, I think) to the chip.
|
||||
// To do this, unsafely cast the string to a byte slice to avoid putting it
|
||||
// in RAM. This is safe in this case because Tx won't write to the 'w'
|
||||
// slice.
|
||||
err = d.bus.Tx(uint16(d.address), unsafeStringToSlice(firmware), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the config data back.
|
||||
// We don't do that, as it slows down configuration and it probably isn't
|
||||
// _really_ necessary with a reasonably stable I2C bus.
|
||||
if false {
|
||||
data := make([]byte, len(firmware)-1)
|
||||
err = d.readn(_FEATURES_IN, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, c := range data {
|
||||
if firmware[i+1] != c {
|
||||
return errConfigMismatch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable sensors.
|
||||
err = d.write1(_INIT_CTRL, 0x01)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait until the device is initialized.
|
||||
start := time.Now()
|
||||
status := uint8(0) // busy
|
||||
for status == 0 {
|
||||
status, err = d.read1(_INTERNAL_STATUS)
|
||||
if err != nil {
|
||||
return err // I2C bus error.
|
||||
}
|
||||
if status > 1 {
|
||||
// Expected either 0 ("not_init") or 1 ("init_ok").
|
||||
return errInitFailed
|
||||
}
|
||||
if time.Since(start) >= 150*time.Millisecond {
|
||||
// The datasheet says initialization should not take longer than
|
||||
return errTimeout
|
||||
}
|
||||
// Don't bother the chip all the time while it's initializing.
|
||||
time.Sleep(50 * time.Microsecond)
|
||||
}
|
||||
|
||||
if config.Features&FeatureStepCounting != 0 {
|
||||
// Enable step counter.
|
||||
// TODO: support step counter parameters.
|
||||
var buf [71]byte
|
||||
buf[0] = _FEATURES_IN // prefix buf with the command
|
||||
data := buf[1:]
|
||||
err = d.readn(_FEATURES_IN, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[0x3A+1] |= 0x10 // enable step counting by setting a magical bit
|
||||
err = d.bus.Tx(uint16(d.address), buf[:], nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the accelerometer.
|
||||
err = d.write1(_PWR_CTRL, 0x04)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure accelerometer for low power usage:
|
||||
// acc_perf_mode=0 (power saving enabled)
|
||||
// acc_bwp=osr4_avg1 (no averaging)
|
||||
// acc_odr=50Hz (50Hz sampling interval, enough for the step counter)
|
||||
const accelConf = 0x00<<7 | 0x00<<4 | 0x07<<0
|
||||
err = d.write1(_ACC_CONF, accelConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Reduce current consumption.
|
||||
// With power saving enabled (and the above ACC_CONF) the chip consumes only
|
||||
// 14µA.
|
||||
err = d.write1(_PWR_CONF, 0x03)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) Update(which drivers.Measurement) error {
|
||||
// TODO: combine temperature and step counter into a single read.
|
||||
if which&drivers.Temperature != 0 {
|
||||
val, err := d.read1(_TEMPERATURE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.combinedTempSteps[4] = val
|
||||
}
|
||||
if which&drivers.Acceleration != 0 {
|
||||
// The acceleration data is stored in DATA8 through DATA13 as 3 12-bit
|
||||
// values.
|
||||
err := d.readn(_DATA_8, d.accelData[:]) // ACC_X(LSB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = d.readn(_STEP_COUNTER_0, d.combinedTempSteps[:4])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Temperature returns the last read temperature in celsius milli degrees (1°C
|
||||
// is 1000).
|
||||
func (d *Device) Temperature() int32 {
|
||||
// The temperature value is a two's complement number (meaning: signed) in
|
||||
// units of 1 kelvin, with 0 being 23°C.
|
||||
return (int32(int8(d.combinedTempSteps[4])) + 23) * 1000
|
||||
}
|
||||
|
||||
// Acceleration returns the last read acceleration in µg (micro-gravity).
|
||||
// When one of the axes is pointing straight to Earth and the sensor is not
|
||||
// moving the returned value will be around 1000000 or -1000000.
|
||||
func (d *Device) Acceleration() (x, y, z int32) {
|
||||
// Combine raw data from d.accelData (stored as 12-bit signed values) into a
|
||||
// number (0..4095):
|
||||
x = int32(d.accelData[0])>>4 | int32(d.accelData[1])<<4
|
||||
y = int32(d.accelData[2])>>4 | int32(d.accelData[3])<<4
|
||||
z = int32(d.accelData[4])>>4 | int32(d.accelData[5])<<4
|
||||
// Sign extend this number to -2048..2047:
|
||||
x = (x << 20) >> 20
|
||||
y = (y << 20) >> 20
|
||||
z = (z << 20) >> 20
|
||||
// Scale from -512..511 to -1000_000..998_046.
|
||||
// Or, at the maximum range (4g), from -2048..2047 to -2000_000..3998_046.
|
||||
// The formula derived as follows (where 512 is the expected value at 1g):
|
||||
// x = x * 1000_000 / 512
|
||||
// x = x * (1000_000/64) / (512/64)
|
||||
// x = x * 15625 / 8
|
||||
x = x * 15625 / 8
|
||||
y = y * 15625 / 8
|
||||
z = z * 15625 / 8
|
||||
return
|
||||
}
|
||||
|
||||
// Steps returns the number of steps counted since the BMA42x sensor was
|
||||
// initialized.
|
||||
func (d *Device) Steps() (steps uint32) {
|
||||
steps |= uint32(d.combinedTempSteps[0]) << 0
|
||||
steps |= uint32(d.combinedTempSteps[1]) << 8
|
||||
steps |= uint32(d.combinedTempSteps[2]) << 16
|
||||
steps |= uint32(d.combinedTempSteps[3]) << 24
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) read1(register uint8) (uint8, error) {
|
||||
d.dataBuf[0] = register
|
||||
err := d.bus.Tx(uint16(d.address), d.dataBuf[:1], d.dataBuf[1:2])
|
||||
return d.dataBuf[1], err
|
||||
}
|
||||
|
||||
func (d *Device) readn(register uint8, data []byte) error {
|
||||
d.dataBuf[0] = register
|
||||
return d.bus.Tx(uint16(d.address), d.dataBuf[:1], data)
|
||||
}
|
||||
|
||||
func (d *Device) write1(register uint8, data uint8) error {
|
||||
d.dataBuf[0] = register
|
||||
d.dataBuf[1] = data
|
||||
return d.bus.Tx(uint16(d.address), d.dataBuf[:2], nil)
|
||||
}
|
||||
|
||||
func unsafeStringToSlice(s string) []byte {
|
||||
// TODO: use unsafe.Slice(unsafe.StringData(...)) once we require Go 1.20.
|
||||
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(sh.Data)), len(s))
|
||||
}
|
||||
|
||||
func identifyChip(chipID uint8) DeviceType {
|
||||
switch chipID {
|
||||
case 0x11:
|
||||
return DeviceBMA421
|
||||
case 0x13:
|
||||
return DeviceBMA425
|
||||
default:
|
||||
return noDevice
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package bma42x
|
||||
|
||||
const (
|
||||
// I2C registers
|
||||
_CHIP_ID = 0x00
|
||||
_ERR_REG = 0x02
|
||||
_STATUS = 0x03
|
||||
_DATA_0 = 0x0A
|
||||
_DATA_1 = 0x0B
|
||||
_DATA_2 = 0x0C
|
||||
_DATA_3 = 0x0D
|
||||
_DATA_4 = 0x0E
|
||||
_DATA_5 = 0x0F
|
||||
_DATA_6 = 0x10
|
||||
_DATA_7 = 0x11
|
||||
_DATA_8 = 0x12
|
||||
_DATA_9 = 0x13
|
||||
_DATA_10 = 0x14
|
||||
_DATA_11 = 0x15
|
||||
_DATA_12 = 0x16
|
||||
_DATA_13 = 0x17
|
||||
_SENSORTIME_0 = 0x18
|
||||
_SENSORTIME_1 = 0x19
|
||||
_SENSORTIME_2 = 0x1A
|
||||
_EVENT = 0x1B
|
||||
_INT_STATUS_0 = 0x1C
|
||||
_INT_STATUS_1 = 0x1D
|
||||
_STEP_COUNTER_0 = 0x1E
|
||||
_STEP_COUNTER_1 = 0x1F
|
||||
_STEP_COUNTER_2 = 0x20
|
||||
_STEP_COUNTER_3 = 0x21
|
||||
_TEMPERATURE = 0x22
|
||||
_FIFO_LENGTH_0 = 0x24
|
||||
_FIFO_LENGTH_1 = 0x25
|
||||
_FIFO_DATA = 0x26
|
||||
_ACTIVITY_TYPE = 0x27
|
||||
_INTERNAL_STATUS = 0x2A
|
||||
_ACC_CONF = 0x40
|
||||
_ACC_RANGE = 0x41
|
||||
_AUX_CONF = 0x44
|
||||
_FIFO_DOWNS = 0x45
|
||||
_FIFO_WTM_0 = 0x46
|
||||
_FIFO_WTM_1 = 0x47
|
||||
_FIFO_CONFIG_0 = 0x48
|
||||
_FIFO_CONFIG_1 = 0x49
|
||||
_AUX_DEV_ID = 0x4B
|
||||
_AUX_IF_CONF = 0x4C
|
||||
_AUX_RD_ADDR = 0x4D
|
||||
_AUX_WR_ADDR = 0x4E
|
||||
_AUX_WR_DATA = 0x4F
|
||||
_INT1_IO_CTRL = 0x53
|
||||
_INT2_IO_CTRL = 0x54
|
||||
_INT_LATCH = 0x55
|
||||
_INT1_MAP = 0x56
|
||||
_INT2_MAP = 0x57
|
||||
_INT_MAP_DATA = 0x58
|
||||
_INIT_CTRL = 0x59
|
||||
_FEATURES_IN = 0x5E
|
||||
_INTERNAL_ERROR = 0x5F
|
||||
_NVM_CONF = 0x6A
|
||||
_IF_CONF = 0x6B
|
||||
_ACC_SELF_TEST = 0x6D
|
||||
_NV_CONF = 0x70
|
||||
_OFFSET_0 = 0x71
|
||||
_OFFSET_1 = 0x72
|
||||
_OFFSET_2 = 0x73
|
||||
_PWR_CONF = 0x7C
|
||||
_PWR_CTRL = 0x7D
|
||||
_CMD = 0x7E
|
||||
|
||||
// Commands send to regCommand.
|
||||
cmdSoftReset = 0xB6
|
||||
)
|
||||
+124
-13
@@ -3,13 +3,14 @@
|
||||
//
|
||||
// Datasheet:
|
||||
// https://cdn-shop.adafruit.com/datasheets/BST-BME280_DS001-10.pdf
|
||||
//
|
||||
package bme280
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// calibrationCoefficients reads at startup and stores the calibration coefficients
|
||||
@@ -34,11 +35,27 @@ type calibrationCoefficients struct {
|
||||
h6 int8
|
||||
}
|
||||
|
||||
type Oversampling byte
|
||||
type Mode byte
|
||||
type FilterCoefficient byte
|
||||
type Period byte
|
||||
|
||||
// Config contains settings for filtering, sampling, and modes of operation
|
||||
type Config struct {
|
||||
Pressure Oversampling
|
||||
Temperature Oversampling
|
||||
Humidity Oversampling
|
||||
Period Period
|
||||
Mode Mode
|
||||
IIR FilterCoefficient
|
||||
}
|
||||
|
||||
// Device wraps an I2C connection to a BME280 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
calibrationCoefficients calibrationCoefficients
|
||||
Config Config
|
||||
}
|
||||
|
||||
// New creates a new BME280 connection. The I2C bus must already be
|
||||
@@ -52,24 +69,49 @@ func New(bus drivers.I2C) Device {
|
||||
}
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication and
|
||||
// read the calibration coefficientes.
|
||||
// ConfigureWithSettings sets up the device for communication and
|
||||
// read the calibration coefficients.
|
||||
//
|
||||
// The default configuration is the Indoor Navigation settings
|
||||
// from the BME280 datasheet.
|
||||
func (d *Device) Configure() {
|
||||
d.ConfigureWithSettings(Config{})
|
||||
}
|
||||
|
||||
// ConfigureWithSettings sets up the device for communication and
|
||||
// read the calibration coefficients.
|
||||
//
|
||||
// The default configuration if config is left at defaults is
|
||||
// the Indoor Navigation settings from the BME280 datasheet.
|
||||
func (d *Device) ConfigureWithSettings(config Config) {
|
||||
d.Config = config
|
||||
|
||||
// If config is not initialized, use Indoor Navigation defaults.
|
||||
if d.Config == (Config{}) {
|
||||
d.Config = Config{
|
||||
Mode: ModeNormal,
|
||||
Period: Period0_5ms,
|
||||
Temperature: Sampling2X,
|
||||
Humidity: Sampling1X,
|
||||
Pressure: Sampling16X,
|
||||
IIR: Coeff16,
|
||||
}
|
||||
}
|
||||
|
||||
var data [24]byte
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION, data[:])
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION, data[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var h1 [1]byte
|
||||
err = d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION_H1, h1[:])
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION_H1, h1[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var h2lsb [7]byte
|
||||
err = d.bus.ReadRegister(uint8(d.Address), REG_CALIBRATION_H2LSB, h2lsb[:])
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALIBRATION_H2LSB, h2lsb[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -94,23 +136,45 @@ func (d *Device) Configure() {
|
||||
d.calibrationCoefficients.h4 = 0 + (int16(h2lsb[3]) << 4) | (int16(h2lsb[4] & 0x0F))
|
||||
d.calibrationCoefficients.h5 = 0 + (int16(h2lsb[5]) << 4) | (int16(h2lsb[4]) >> 4)
|
||||
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{0x3f})
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL_MEAS_ADDR, []byte{0xB7})
|
||||
d.bus.WriteRegister(uint8(d.Address), CTRL_CONFIG, []byte{0x00})
|
||||
d.Reset()
|
||||
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_CONFIG, []byte{byte(d.Config.Period<<5) | byte(d.Config.IIR<<2)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_HUMIDITY_ADDR, []byte{byte(d.Config.Humidity)})
|
||||
|
||||
// Normal mode, start measuring now
|
||||
if d.Config.Mode == ModeNormal {
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
|
||||
byte(d.Config.Temperature<<5) |
|
||||
byte(d.Config.Pressure<<2) |
|
||||
byte(d.Config.Mode)})
|
||||
}
|
||||
}
|
||||
|
||||
// Connected returns whether a BME280 has been found.
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
|
||||
return data[0] == CHIP_ID
|
||||
}
|
||||
|
||||
// Reset the device
|
||||
func (d *Device) Reset() {
|
||||
d.bus.WriteRegister(uint8(d.Address), CMD_RESET, []byte{0xB6})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), CMD_RESET, []byte{0xB6})
|
||||
}
|
||||
|
||||
// SetMode can set the device to Sleep, Normal or Forced mode
|
||||
//
|
||||
// Calling this method is optional, Configure can be used to set the
|
||||
// initial mode if no mode change is desired. This method is most
|
||||
// useful to switch between Sleep and Normal modes.
|
||||
func (d *Device) SetMode(mode Mode) {
|
||||
d.Config.Mode = mode
|
||||
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
|
||||
byte(d.Config.Temperature<<5) |
|
||||
byte(d.Config.Pressure<<2) |
|
||||
byte(d.Config.Mode)})
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
||||
@@ -149,7 +213,8 @@ func (d *Device) ReadHumidity() (int32, error) {
|
||||
// ReadAltitude returns the current altitude in meters based on the
|
||||
// current barometric pressure and estimated pressure at sea level.
|
||||
// Calculation is based on code from Adafruit BME280 library
|
||||
// https://github.com/adafruit/Adafruit_BME280_Library
|
||||
//
|
||||
// https://github.com/adafruit/Adafruit_BME280_Library
|
||||
func (d *Device) ReadAltitude() (alt int32, err error) {
|
||||
mPa, _ := d.ReadPressure()
|
||||
atmP := float32(mPa) / 100000
|
||||
@@ -186,7 +251,17 @@ func readIntLE(msb byte, lsb byte) int16 {
|
||||
// readData does a burst read from 0xF7 to 0xF0 according to the datasheet
|
||||
// resulting in an slice with 8 bytes 0-2 = pressure / 3-5 = temperature / 6-7 = humidity
|
||||
func (d *Device) readData() (data [8]byte, err error) {
|
||||
err = d.bus.ReadRegister(uint8(d.Address), REG_PRESSURE, data[:])
|
||||
if d.Config.Mode == ModeForced {
|
||||
// Write the CTRL_MEAS register to trigger a measurement
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), CTRL_MEAS_ADDR, []byte{
|
||||
byte(d.Config.Temperature<<5) |
|
||||
byte(d.Config.Pressure<<2) |
|
||||
byte(d.Config.Mode)})
|
||||
|
||||
time.Sleep(d.measurementDelay())
|
||||
}
|
||||
|
||||
err = legacy.ReadRegister(d.bus, uint8(d.Address), REG_PRESSURE, data[:])
|
||||
if err != nil {
|
||||
println(err)
|
||||
return
|
||||
@@ -256,3 +331,39 @@ func (d *Device) calculateHumidity(data [8]byte, tFine int32) int32 {
|
||||
return int32(100 * h)
|
||||
|
||||
}
|
||||
|
||||
// measurementDelay returns how much time each measurement will take
|
||||
// on the device.
|
||||
//
|
||||
// This is used in forced mode to wait until a measurement is complete.
|
||||
func (d *Device) measurementDelay() time.Duration {
|
||||
const MeasOffset = 1250
|
||||
const MeasDur = 2300
|
||||
const HumMeasOffset = 575
|
||||
const MeasScalingFactor = 1000
|
||||
|
||||
// delay is based on over-sampling rate - this table converts from
|
||||
// setting to number samples
|
||||
sampleRateConv := []int{0, 1, 2, 4, 8, 16}
|
||||
|
||||
tempOsr := 16
|
||||
if d.Config.Temperature <= Sampling16X {
|
||||
tempOsr = sampleRateConv[d.Config.Temperature]
|
||||
}
|
||||
|
||||
presOsr := 16
|
||||
if d.Config.Temperature <= Sampling16X {
|
||||
presOsr = sampleRateConv[d.Config.Pressure]
|
||||
}
|
||||
|
||||
humOsr := 16
|
||||
if d.Config.Temperature <= Sampling16X {
|
||||
humOsr = sampleRateConv[d.Config.Humidity]
|
||||
}
|
||||
|
||||
max_delay := ((MeasOffset + (MeasDur * tempOsr) +
|
||||
((MeasDur * presOsr) + HumMeasOffset) +
|
||||
((MeasDur * humOsr) + HumMeasOffset)) / MeasScalingFactor)
|
||||
|
||||
return time.Duration(max_delay) * time.Millisecond
|
||||
}
|
||||
|
||||
@@ -20,6 +20,50 @@ const (
|
||||
CHIP_ID = 0x60
|
||||
)
|
||||
|
||||
// Increasing sampling rate increases precision but also the wait time for measurements. The datasheet has a table of
|
||||
// suggested values for oversampling, output data rates, and iir filter coefficients by use case.
|
||||
const (
|
||||
SamplingOff Oversampling = iota
|
||||
Sampling1X
|
||||
Sampling2X
|
||||
Sampling4X
|
||||
Sampling8X
|
||||
Sampling16X
|
||||
)
|
||||
|
||||
// In normal mode (the default) the sensor takes masurements periodically. In forced
|
||||
// mode, the sensor takes a measurement only when requested.
|
||||
//
|
||||
// For use-cases with infrequent sampling, forced mode is more power efficient.
|
||||
const (
|
||||
ModeNormal Mode = 0x03
|
||||
ModeForced Mode = 0x01
|
||||
ModeSleep Mode = 0x00
|
||||
)
|
||||
|
||||
// IIR filter coefficients, higher values means steadier measurements but slower reaction times
|
||||
const (
|
||||
Coeff0 FilterCoefficient = iota
|
||||
Coeff2
|
||||
Coeff4
|
||||
Coeff8
|
||||
Coeff16
|
||||
)
|
||||
|
||||
// Period of standby in normal mode which controls how often measurements are taken
|
||||
//
|
||||
// Note Period10ms and Period20ms are out of sequence, but are per the datasheet
|
||||
const (
|
||||
Period0_5ms Period = 0b000
|
||||
Period62_5ms = 0b001
|
||||
Period125ms = 0b010
|
||||
Period250ms = 0b011
|
||||
Period500ms = 0b100
|
||||
Period1000ms = 0b101
|
||||
Period10ms = 0b110
|
||||
Period20ms = 0b111
|
||||
)
|
||||
|
||||
const (
|
||||
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
|
||||
)
|
||||
|
||||
+58
-29
@@ -1,26 +1,36 @@
|
||||
package bmi160
|
||||
|
||||
import "machine"
|
||||
import (
|
||||
"time"
|
||||
|
||||
import "time"
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// DeviceSPI is the SPI interface to a BMI160 accelerometer/gyroscope. There is
|
||||
// also an I2C interface, but it is not yet supported.
|
||||
type DeviceSPI struct {
|
||||
// Chip select pin
|
||||
CSB machine.Pin
|
||||
csb pin.OutputFunc
|
||||
|
||||
buf [7]byte
|
||||
|
||||
// SPI bus (requires chip select to be usable).
|
||||
Bus machine.SPI
|
||||
bus drivers.SPI
|
||||
configurePins func()
|
||||
}
|
||||
|
||||
// NewSPI returns a new device driver. The pin and SPI interface are not
|
||||
// touched, provide a fully configured SPI object and call Configure to start
|
||||
// using this device.
|
||||
func NewSPI(csb machine.Pin, spi machine.SPI) *DeviceSPI {
|
||||
func NewSPI(csb pin.Output, spi drivers.SPI) *DeviceSPI {
|
||||
return &DeviceSPI{
|
||||
CSB: csb, // chip select
|
||||
Bus: spi,
|
||||
csb: csb.Set, // chip select
|
||||
bus: spi,
|
||||
configurePins: func() {
|
||||
legacy.ConfigurePinOut(csb)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +38,11 @@ func NewSPI(csb machine.Pin, spi machine.SPI) *DeviceSPI {
|
||||
// configures the BMI160, but it does not configure the SPI interface (it is
|
||||
// assumed to be up and running).
|
||||
func (d *DeviceSPI) Configure() error {
|
||||
d.CSB.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
d.CSB.High()
|
||||
|
||||
if d.configurePins == nil {
|
||||
return legacy.ErrConfigBeforeInstantiated
|
||||
}
|
||||
d.configurePins()
|
||||
d.csb.High()
|
||||
// The datasheet recommends doing a register read from address 0x7F to get
|
||||
// SPI communication going:
|
||||
// > If CSB sees a rising edge after power-up, the BMI160 interface switches
|
||||
@@ -77,10 +89,13 @@ func (d *DeviceSPI) Reset() error {
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
|
||||
func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
|
||||
data := []byte{0x80 | reg_TEMPERATURE_0, 0, 0}
|
||||
d.CSB.Low()
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
data := d.buf[:3]
|
||||
data[0] = 0x80 | reg_TEMPERATURE_0
|
||||
data[1] = 0
|
||||
data[2] = 0
|
||||
d.csb.Low()
|
||||
err = d.bus.Tx(data, data)
|
||||
d.csb.High()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -110,10 +125,14 @@ func (d *DeviceSPI) ReadTemperature() (temperature int32, err error) {
|
||||
// and the sensor is not moving the returned value will be around 1000000 or
|
||||
// -1000000.
|
||||
func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
|
||||
data := []byte{0x80 | reg_ACC_XL, 0, 0, 0, 0, 0, 0}
|
||||
d.CSB.Low()
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
data := d.buf[:7]
|
||||
data[0] = 0x80 | reg_ACC_XL
|
||||
for i := 1; i < len(data); i++ {
|
||||
data[i] = 0
|
||||
}
|
||||
d.csb.Low()
|
||||
err = d.bus.Tx(data, data)
|
||||
d.csb.High()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -136,10 +155,14 @@ func (d *DeviceSPI) ReadAcceleration() (x int32, y int32, z int32, err error) {
|
||||
// rotation along one axis and while doing so integrate all values over time,
|
||||
// you would get a value close to 360000000.
|
||||
func (d *DeviceSPI) ReadRotation() (x int32, y int32, z int32, err error) {
|
||||
data := []byte{0x80 | reg_GYR_XL, 0, 0, 0, 0, 0, 0}
|
||||
d.CSB.Low()
|
||||
err = d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
data := d.buf[:7]
|
||||
data[0] = 0x80 | reg_GYR_XL
|
||||
for i := 1; i < len(data); i++ {
|
||||
data[i] = 0
|
||||
}
|
||||
d.csb.Low()
|
||||
err = d.bus.Tx(data, data)
|
||||
d.csb.High()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -182,10 +205,12 @@ func (d *DeviceSPI) readRegister(address uint8) uint8 {
|
||||
// I don't know why but it appears necessary to sleep for a bit here.
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
data := []byte{0x80 | address, 0}
|
||||
d.CSB.Low()
|
||||
d.Bus.Tx(data, data)
|
||||
d.CSB.High()
|
||||
data := d.buf[:2]
|
||||
data[0] = 0x80 | address
|
||||
data[1] = 0
|
||||
d.csb.Low()
|
||||
d.bus.Tx(data, data)
|
||||
d.csb.High()
|
||||
return data[1]
|
||||
}
|
||||
|
||||
@@ -195,7 +220,11 @@ func (d *DeviceSPI) writeRegister(address, data uint8) {
|
||||
// I don't know why but it appears necessary to sleep for a bit here.
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
d.CSB.Low()
|
||||
d.Bus.Tx([]byte{address, data}, []byte{0, 0})
|
||||
d.CSB.High()
|
||||
buf := d.buf[:2]
|
||||
buf[0] = address
|
||||
buf[1] = data
|
||||
|
||||
d.csb.Low()
|
||||
d.bus.Tx(buf, buf)
|
||||
d.csb.High()
|
||||
}
|
||||
|
||||
+23
-7
@@ -3,13 +3,14 @@
|
||||
//
|
||||
// Datasheet:
|
||||
// https://cdn-shop.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf
|
||||
//
|
||||
package bmp180 // import "tinygo.org/x/drivers/bmp180"
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// OversamplingMode is the oversampling ratio of the pressure measurement.
|
||||
@@ -55,7 +56,7 @@ func New(bus drivers.I2C) Device {
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := []byte{0}
|
||||
d.bus.ReadRegister(uint8(d.Address), WHO_AM_I, data)
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), WHO_AM_I, data)
|
||||
return data[0] == CHIP_ID
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ func (d *Device) Connected() bool {
|
||||
// read the calibration coefficients.
|
||||
func (d *Device) Configure() {
|
||||
data := make([]byte, 22)
|
||||
err := d.bus.ReadRegister(uint8(d.Address), AC1_MSB, data)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), AC1_MSB, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -124,12 +125,27 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
|
||||
return 1000 * (p + ((x1 + x2 + 3791) >> 4)), nil
|
||||
}
|
||||
|
||||
// ReadAltitude returns the current altitude in meters based on the
|
||||
// current barometric pressure and estimated pressure at sea level.
|
||||
// Calculation is based on code from Adafruit BME280 library
|
||||
//
|
||||
// https://github.com/adafruit/Adafruit_BME280_Library
|
||||
func (d *Device) ReadAltitude() (int32, error) {
|
||||
mPa, err := d.ReadPressure()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
atmP := float32(mPa) / 100000
|
||||
|
||||
return int32(44330.0 * (1.0 - math.Pow(float64(atmP/SEALEVEL_PRESSURE), 0.1903))), nil
|
||||
}
|
||||
|
||||
// rawTemp returns the sensor's raw values of the temperature
|
||||
func (d *Device) rawTemp() (int32, error) {
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
data := make([]byte, 2)
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_TEMP_MSB, data)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_TEMP_MSB, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -145,10 +161,10 @@ func (d *Device) calculateB5(rawTemp int32) int32 {
|
||||
|
||||
// rawPressure returns the sensor's raw values of the pressure
|
||||
func (d *Device) rawPressure(mode OversamplingMode) (int32, error) {
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_PRESSURE + byte(mode<<6)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL, []byte{CMD_PRESSURE + byte(mode<<6)})
|
||||
time.Sleep(pauseForReading(mode))
|
||||
data := make([]byte, 3)
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_PRESSURE_MSB, data)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_PRESSURE_MSB, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -28,3 +28,7 @@ const (
|
||||
// ULTRAHIGHRESOLUTION is the highest oversampling mode of the pressure measurement.
|
||||
ULTRAHIGHRESOLUTION
|
||||
)
|
||||
|
||||
const (
|
||||
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
|
||||
)
|
||||
|
||||
+15
-15
@@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// OversamplingMode is the oversampling ratio of the temperature or pressure measurement.
|
||||
@@ -22,6 +23,7 @@ type Filter uint
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
buf [6]byte
|
||||
cali calibrationCoefficients
|
||||
Temperature Oversampling
|
||||
Pressure Oversampling
|
||||
@@ -64,14 +66,14 @@ func New(bus drivers.I2C) Device {
|
||||
// It does a "who am I" request and checks the response.
|
||||
func (d *Device) Connected() bool {
|
||||
data := make([]byte, 1)
|
||||
d.bus.ReadRegister(uint8(d.Address), REG_ID, data)
|
||||
legacy.ReadRegister(d.bus, uint8(d.Address), REG_ID, data)
|
||||
return data[0] == CHIP_ID
|
||||
}
|
||||
|
||||
// Reset preforms complete power-on-reset procedure.
|
||||
// It is required to call Configure afterwards.
|
||||
func (d *Device) Reset() {
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_RESET, []byte{CMD_RESET})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_RESET, []byte{CMD_RESET})
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication and
|
||||
@@ -85,15 +87,15 @@ func (d *Device) Configure(standby Standby, filter Filter, temp Oversampling, pr
|
||||
|
||||
// Write the configuration (standby, filter, spi 3 wire)
|
||||
config := uint(d.Standby<<5) | uint(d.Filter<<2) | 0x00
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_CONFIG, []byte{byte(config)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CONFIG, []byte{byte(config)})
|
||||
|
||||
// Write the control (temperature oversampling, pressure oversampling,
|
||||
config = uint(d.Temperature<<5) | uint(d.Pressure<<2) | uint(d.Mode)
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
|
||||
|
||||
// Read Calibration data
|
||||
data := make([]byte, 24)
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_CALI, data)
|
||||
err := legacy.ReadRegister(d.bus, uint8(d.Address), REG_CALI, data)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -133,8 +135,8 @@ func (d *Device) PrintCali() {
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000).
|
||||
func (d *Device) ReadTemperature() (temperature int32, err error) {
|
||||
data, err := d.readData(REG_TEMP, 3)
|
||||
if err != nil {
|
||||
data := d.buf[:3]
|
||||
if err = d.readData(REG_TEMP, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -157,8 +159,8 @@ func (d *Device) ReadTemperature() (temperature int32, err error) {
|
||||
// ReadPressure returns the pressure in milli pascals (mPa).
|
||||
func (d *Device) ReadPressure() (pressure int32, err error) {
|
||||
// First 3 bytes are Pressure, last 3 bytes are Temperature
|
||||
data, err := d.readData(REG_PRES, 6)
|
||||
if err != nil {
|
||||
data := d.buf[:6]
|
||||
if err = d.readData(REG_PRES, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -202,24 +204,22 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
|
||||
}
|
||||
|
||||
// readData reads n number of bytes of the specified register
|
||||
func (d *Device) readData(register int, n int) ([]byte, error) {
|
||||
func (d *Device) readData(register int, data []byte) error {
|
||||
// If not in normal mode, set the mode to FORCED mode, to prevent incorrect measurements
|
||||
// After the measurement in FORCED mode, the sensor will return to SLEEP mode
|
||||
if d.Mode != MODE_NORMAL {
|
||||
config := uint(d.Temperature<<5) | uint(d.Pressure<<2) | uint(MODE_FORCED)
|
||||
d.bus.WriteRegister(uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
|
||||
legacy.WriteRegister(d.bus, uint8(d.Address), REG_CTRL_MEAS, []byte{byte(config)})
|
||||
}
|
||||
|
||||
// Check STATUS register, wait if data is not available yet
|
||||
status := make([]byte, 1)
|
||||
for d.bus.ReadRegister(uint8(d.Address), uint8(REG_STATUS), status[0:]); status[0] != 4 && status[0] != 0; d.bus.ReadRegister(uint8(d.Address), uint8(REG_STATUS), status[0:]) {
|
||||
for legacy.ReadRegister(d.bus, uint8(d.Address), uint8(REG_STATUS), status[0:]); status[0] != 4 && status[0] != 0; legacy.ReadRegister(d.bus, uint8(d.Address), uint8(REG_STATUS), status[0:]) {
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
// Read the requested register
|
||||
data := make([]byte, n)
|
||||
err := d.bus.ReadRegister(uint8(d.Address), uint8(register), data[:])
|
||||
return data, err
|
||||
return legacy.ReadRegister(d.bus, uint8(d.Address), uint8(register), data[:])
|
||||
}
|
||||
|
||||
// convert3Bytes converts three bytes to int32
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
package bmp388
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
var (
|
||||
errConfigWrite = errors.New("bmp388: failed to configure sensor, check connection")
|
||||
errConfig = errors.New("bmp388: there is a problem with the configuration, try reducing ODR")
|
||||
errCaliRead = errors.New("bmp388: failed to read calibration coefficient register")
|
||||
errSoftReset = errors.New("bmp388: failed to perform a soft reset")
|
||||
errNotConnected = errors.New("bmp388: not connected")
|
||||
)
|
||||
|
||||
type Oversampling byte
|
||||
type Mode byte
|
||||
type OutputDataRate byte
|
||||
type FilterCoefficient byte
|
||||
|
||||
// Config contains settings for filtering, sampling, and modes of operation
|
||||
type Config struct {
|
||||
Pressure Oversampling
|
||||
Temperature Oversampling
|
||||
Mode Mode
|
||||
ODR OutputDataRate
|
||||
IIR FilterCoefficient
|
||||
}
|
||||
|
||||
// Device wraps the I2C connection and configuration values for the BMP388
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint8
|
||||
cali calibrationCoefficients
|
||||
Config Config
|
||||
}
|
||||
|
||||
type calibrationCoefficients struct {
|
||||
// Temperature compensation
|
||||
t1 uint16
|
||||
t2 uint16
|
||||
t3 int8
|
||||
|
||||
// Pressure compensation
|
||||
p1 int16
|
||||
p2 int16
|
||||
p3 int8
|
||||
p4 int8
|
||||
p5 uint16
|
||||
p6 uint16
|
||||
p7 int8
|
||||
p8 int8
|
||||
p9 int16
|
||||
p10 int8
|
||||
p11 int8
|
||||
}
|
||||
|
||||
// New returns a bmp388 struct with the default I2C address. Configure must also be called after instanting
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure can enable settings on the BMP388 and reads the calibration coefficients
|
||||
func (d *Device) Configure(config Config) (err error) {
|
||||
d.Config = config
|
||||
|
||||
if d.Config == (Config{}) {
|
||||
d.Config.Mode = Normal
|
||||
}
|
||||
|
||||
// Turning on the pressure and temperature sensors and setting the measurement mode
|
||||
err = d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
|
||||
|
||||
// Configure the oversampling, output data rate, and iir filter coefficient settings
|
||||
err = d.writeRegister(RegOSR, byte(d.Config.Pressure|d.Config.Temperature<<3))
|
||||
err = d.writeRegister(RegODR, byte(d.Config.ODR))
|
||||
err = d.writeRegister(RegIIR, byte(d.Config.IIR<<1))
|
||||
|
||||
if err != nil {
|
||||
return errConfigWrite
|
||||
}
|
||||
|
||||
// Check if there is a problem with the given configuration
|
||||
if d.configurationError() {
|
||||
return errConfig
|
||||
}
|
||||
|
||||
// Reading the builtin calibration coefficients and parsing them per the datasheet. The compensation formula given
|
||||
// in the datasheet is implemented in floating point
|
||||
buffer, err := d.readRegister(RegCali, 21)
|
||||
if err != nil {
|
||||
return errCaliRead
|
||||
}
|
||||
|
||||
d.cali.t1 = uint16(buffer[1])<<8 | uint16(buffer[0])
|
||||
d.cali.t2 = uint16(buffer[3])<<8 | uint16(buffer[2])
|
||||
d.cali.t3 = int8(buffer[4])
|
||||
|
||||
d.cali.p1 = int16(buffer[6])<<8 | int16(buffer[5])
|
||||
d.cali.p2 = int16(buffer[8])<<8 | int16(buffer[7])
|
||||
d.cali.p3 = int8(buffer[9])
|
||||
d.cali.p4 = int8(buffer[10])
|
||||
d.cali.p5 = uint16(buffer[12])<<8 | uint16(buffer[11])
|
||||
d.cali.p6 = uint16(buffer[14])<<8 | uint16(buffer[13])
|
||||
d.cali.p7 = int8(buffer[15])
|
||||
d.cali.p8 = int8(buffer[16])
|
||||
d.cali.p9 = int16(buffer[18])<<8 | int16(buffer[17])
|
||||
d.cali.p10 = int8(buffer[19])
|
||||
d.cali.p11 = int8(buffer[20])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the temperature registers and compute a compensation value for the temperature and pressure compensation
|
||||
// calculations. This is not the temperature itself.
|
||||
func (d *Device) tlinCompensate() (int64, error) {
|
||||
rawTemp, err := d.readSensorData(RegTemp)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// pulled from C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
|
||||
partialData1 := rawTemp - (256 * int64(d.cali.t1))
|
||||
partialData2 := int64(d.cali.t2) * partialData1
|
||||
partialData3 := (partialData1 * partialData1)
|
||||
partialData4 := partialData3 * int64(d.cali.t3)
|
||||
partialData5 := (partialData2 * 262144) + partialData4
|
||||
return partialData5 / 4294967296, nil
|
||||
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in centicelsius, i.e 2426 / 100 = 24.26 C
|
||||
func (d *Device) ReadTemperature() (int32, error) {
|
||||
|
||||
tlin, err := d.tlinCompensate()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
temp := (tlin * 25) / 16384
|
||||
return int32(temp), nil
|
||||
}
|
||||
|
||||
// ReadPressure returns the pressure in centipascals, i.e 10132520 / 100 = 101325.20 Pa
|
||||
func (d *Device) ReadPressure() (int32, error) {
|
||||
|
||||
tlin, err := d.tlinCompensate()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
rawPress, err := d.readSensorData(RegPress)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// code pulled from bmp388 C driver: https://github.com/BoschSensortec/BMP3-Sensor-API/blob/master/bmp3.c
|
||||
partialData1 := tlin * tlin
|
||||
partialData2 := partialData1 / 64
|
||||
partialData3 := (partialData2 * tlin) / 256
|
||||
partialData4 := (int64(d.cali.p8) * partialData3) / 32
|
||||
partialData5 := (int64(d.cali.p7) * partialData1) * 16
|
||||
partialData6 := (int64(d.cali.p6) * tlin) * 4194304
|
||||
offset := (int64(d.cali.p5) * 140737488355328) + partialData4 + partialData5 + partialData6
|
||||
partialData2 = (int64(d.cali.p4) * partialData3) / 32
|
||||
partialData4 = (int64(d.cali.p3) * partialData1) * 4
|
||||
partialData5 = (int64(d.cali.p2) - 16384) * tlin * 2097152
|
||||
sensitivity := ((int64(d.cali.p1) - 16384) * 70368744177664) + partialData2 + partialData4 + partialData5
|
||||
partialData1 = (sensitivity / 16777216) * rawPress
|
||||
partialData2 = int64(d.cali.p10) * tlin
|
||||
partialData3 = partialData2 + (65536 * int64(d.cali.p9))
|
||||
partialData4 = (partialData3 * rawPress) / 8192
|
||||
|
||||
// dividing by 10 followed by multiplying by 10
|
||||
// To avoid overflow caused by (pressure * partial_data4)
|
||||
partialData5 = (rawPress * (partialData4 / 10)) / 512
|
||||
partialData5 = partialData5 * 10
|
||||
partialData6 = (int64)(uint64(rawPress) * uint64(rawPress))
|
||||
partialData2 = (int64(d.cali.p11) * partialData6) / 65536
|
||||
partialData3 = (partialData2 * rawPress) / 128
|
||||
partialData4 = (offset / 4) + partialData1 + partialData5 + partialData3
|
||||
compPress := ((uint64(partialData4) * 25) / uint64(1099511627776))
|
||||
return int32(compPress), nil
|
||||
}
|
||||
|
||||
// SoftReset commands the BMP388 to reset of all user configuration settings
|
||||
func (d *Device) SoftReset() error {
|
||||
err := d.writeRegister(RegCmd, SoftReset)
|
||||
if err != nil {
|
||||
return errSoftReset
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connected tries to reach the bmp388 and check its chip id register. Returns true if it was able to successfully
|
||||
// communicate over i2c and returns the correct value
|
||||
func (d *Device) Connected() bool {
|
||||
data, err := d.readRegister(RegChipId, 1)
|
||||
return err == nil && data[0] == ChipId // returns true if i2c comm was good and response equals 0x50
|
||||
}
|
||||
|
||||
// SetMode changes the run mode of the sensor, NORMAL is the one to use for most cases. Use FORCED if you plan to take
|
||||
// measurements infrequently and want to conserve power. SLEEP will of course put the sensor to sleep
|
||||
func (d *Device) SetMode(mode Mode) error {
|
||||
d.Config.Mode = mode
|
||||
return d.writeRegister(RegPwrCtrl, PwrPress|PwrTemp|byte(d.Config.Mode))
|
||||
}
|
||||
|
||||
func (d *Device) readSensorData(register byte) (data int64, err error) {
|
||||
|
||||
if !d.Connected() {
|
||||
return 0, errNotConnected
|
||||
}
|
||||
|
||||
// put the sensor back into forced mode to get a reading, the sensor goes back to sleep after taking one read in
|
||||
// forced mode
|
||||
if d.Config.Mode != Normal {
|
||||
err = d.SetMode(Forced)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
bytes, err := d.readRegister(register, 3)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
data = int64(bytes[2])<<16 | int64(bytes[1])<<8 | int64(bytes[0])
|
||||
return
|
||||
}
|
||||
|
||||
// configurationError checks the register error for the configuration error bit. The bit is cleared on read by the bmp.
|
||||
func (d *Device) configurationError() bool {
|
||||
data, err := d.readRegister(RegErr, 1)
|
||||
return err == nil && (data[0]&0x04) != 0
|
||||
}
|
||||
|
||||
func (d *Device) readRegister(register byte, len int) (data []byte, err error) {
|
||||
data = make([]byte, len)
|
||||
err = legacy.ReadRegister(d.bus, d.Address, register, data)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *Device) writeRegister(register byte, data byte) error {
|
||||
return legacy.WriteRegister(d.bus, d.Address, register, []byte{data})
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Package bmp388 provides a driver for Bosch's BMP388 digital temperature & pressure sensor.
|
||||
// The datasheet can be found here: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf
|
||||
package bmp388
|
||||
|
||||
const Address byte = 0x77 // default I2C address
|
||||
|
||||
const (
|
||||
RegChipId byte = 0x00 // useful for checking the connection
|
||||
RegCali byte = 0x31 // pressure & temperature compensation calibration coefficients
|
||||
RegPress byte = 0x04 // start of pressure data registers
|
||||
RegTemp byte = 0x07 // start of temperature data registers
|
||||
RegPwrCtrl byte = 0x1B // measurement mode & pressure/temperature sensor power register
|
||||
RegOSR byte = 0x1C // oversampling settings register
|
||||
RegODR byte = 0x1D //
|
||||
RegCmd byte = 0x7E // miscellaneous command register
|
||||
RegStat byte = 0x03 // sensor status register
|
||||
RegErr byte = 0x02 // error status register
|
||||
RegIIR byte = 0x1F
|
||||
)
|
||||
|
||||
const (
|
||||
ChipId byte = 0x50 // correct response if reading from chip id register
|
||||
PwrPress byte = 0x01 // power on pressure sensor
|
||||
PwrTemp byte = 0x02 // power on temperature sensor
|
||||
SoftReset byte = 0xB6 // command to reset all user configuration
|
||||
DRDYPress byte = 0x20 // for checking if pressure data is ready
|
||||
DRDYTemp byte = 0x40 // for checking if pressure data is ready
|
||||
)
|
||||
|
||||
// The difference between forced and normal mode is the bmp388 goes to sleep after taking a measurement in forced mode.
|
||||
// Set it to forced if you intend to take measurements sporadically and want to save power. The driver will handle
|
||||
// waking the sensor up when the sensor is in forced mode.
|
||||
const (
|
||||
Normal Mode = 0x30
|
||||
Forced Mode = 0x16
|
||||
Sleep Mode = 0x00
|
||||
)
|
||||
|
||||
// Increasing sampling rate increases precision but also the wait time for measurements. The datasheet has a table of
|
||||
// suggested values for oversampling, output data rates, and iir filter coefficients by use case.
|
||||
const (
|
||||
Sampling1X Oversampling = iota
|
||||
Sampling2X
|
||||
Sampling4X
|
||||
Sampling8X
|
||||
Sampling16X
|
||||
Sampling32X
|
||||
)
|
||||
|
||||
// Output data rates in Hz. If increasing the sampling rates you need to decrease the output data rates, else the bmp388
|
||||
// will freeze and Configure() will return a configuration error message. In that case keep decreasing the data rate
|
||||
// until the bmp is happy
|
||||
const (
|
||||
Odr200 OutputDataRate = iota
|
||||
Odr100
|
||||
Odr50
|
||||
Odr25
|
||||
Odr12p5
|
||||
Odr6p25
|
||||
Odr3p1
|
||||
Odr1p5
|
||||
Odr0p78
|
||||
Odr0p39
|
||||
Odr0p2
|
||||
Odr0p1
|
||||
Odr0p05
|
||||
Odr0p02
|
||||
Odr0p01
|
||||
Odr0p006
|
||||
Odr0p003
|
||||
Odr0p0015
|
||||
)
|
||||
|
||||
// IIR filter coefficients, higher values means steadier measurements but slower reaction times
|
||||
const (
|
||||
Coeff0 FilterCoefficient = iota
|
||||
Coeff1
|
||||
Coeff3
|
||||
Coeff7
|
||||
Coeff15
|
||||
Coeff31
|
||||
Coeff63
|
||||
Coeff127
|
||||
)
|
||||
@@ -0,0 +1,256 @@
|
||||
// Package bno08x provides a TinyGo driver for the Adafruit BNO08x 9-DOF IMU sensors.
|
||||
//
|
||||
// This driver implements the CEVA SH-2 protocol over the SHTP transport layer,
|
||||
// providing access to orientation, motion, and environmental sensors.
|
||||
//
|
||||
// Datasheet: https://www.ceva-ip.com/wp-content/uploads/BNO080_085-Datasheet.pdf
|
||||
package bno08x
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// Buser is the interface that wraps I2C or SPI bus operations.
|
||||
type Buser interface {
|
||||
configure(address uint16, readChunk int) error
|
||||
read(target []byte) (int, uint32, error)
|
||||
write(data []byte) error
|
||||
softReset() error
|
||||
}
|
||||
|
||||
// Device represents a BNO08x sensor device.
|
||||
type Device struct {
|
||||
bus Buser
|
||||
resetPin pin.OutputFunc
|
||||
|
||||
hal *hal
|
||||
shtp *shtp
|
||||
sh2 *sh2Protocol
|
||||
|
||||
queue [8]SensorValue
|
||||
queueHead int
|
||||
queueTail int
|
||||
queueCount int
|
||||
|
||||
productIDs ProductIDs
|
||||
lastReset bool
|
||||
}
|
||||
|
||||
// Config holds configuration options for the device.
|
||||
type Config struct {
|
||||
// Address is the I2C address (used only for I2C bus).
|
||||
Address uint16
|
||||
|
||||
// ResetPin is the optional hardware reset pin.
|
||||
ResetPin pin.OutputFunc
|
||||
|
||||
// ReadChunk is the I2C read chunk size (used only for I2C bus).
|
||||
ReadChunk int
|
||||
|
||||
// StartupDelay is the delay after reset (default: 100ms).
|
||||
StartupDelay time.Duration
|
||||
}
|
||||
|
||||
// Configure initializes the sensor and prepares it for use.
|
||||
func (d *Device) Configure(cfg Config) error {
|
||||
// Configure bus-specific settings
|
||||
if err := d.bus.configure(cfg.Address, cfg.ReadChunk); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.ResetPin != nil {
|
||||
d.resetPin = cfg.ResetPin
|
||||
}
|
||||
if cfg.StartupDelay <= 0 {
|
||||
cfg.StartupDelay = 100 * time.Millisecond
|
||||
}
|
||||
|
||||
d.hal = newHAL(d)
|
||||
d.shtp = newSHTP(d.hal)
|
||||
d.sh2 = newSH2Protocol(d)
|
||||
|
||||
d.queueHead = 0
|
||||
d.queueTail = 0
|
||||
d.queueCount = 0
|
||||
d.productIDs = ProductIDs{}
|
||||
d.lastReset = false
|
||||
|
||||
if err := d.hal.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now that handlers are registered, perform reset
|
||||
// Try hardware reset first if available
|
||||
if d.resetPin != nil {
|
||||
d.hardwareReset()
|
||||
time.Sleep(cfg.StartupDelay)
|
||||
} else {
|
||||
// No hardware reset pin - try soft reset via bus
|
||||
if err := d.bus.softReset(); err != nil {
|
||||
// If that fails, try soft reset via SHTP protocol
|
||||
_ = d.sh2.softReset()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for reset notification by actively polling
|
||||
// The sensor should send reset complete message shortly after reset
|
||||
deadline := time.Now().Add(1000 * time.Millisecond)
|
||||
pollCount := 0
|
||||
for time.Now().Before(deadline) {
|
||||
pollCount++
|
||||
if err := d.service(); err != nil {
|
||||
// Ignore errors during initial polling - sensor might not be ready
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
if d.lastReset {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
if !d.lastReset {
|
||||
return errTimeout
|
||||
}
|
||||
|
||||
// NOTE: We intentionally skip the Initialize command (sh2_initialize)
|
||||
// Testing revealed that sending the Initialize command (0xF2 0x00 0x04 0x01...)
|
||||
// prevents the BNO08x from sending sensor reports on channel 3.
|
||||
// The sensor works correctly without this command after a soft reset.
|
||||
// The Arduino library likely works because it does a hardware reset which
|
||||
// may put the sensor in a different state, or their initialization sequence
|
||||
// differs in a way that doesn't trigger this issue.
|
||||
|
||||
// Request product IDs
|
||||
if err := d.sh2.requestProductIDs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait for product IDs with polling delay
|
||||
deadline = time.Now().Add(500 * time.Millisecond)
|
||||
for time.Now().Before(deadline) {
|
||||
if err := d.service(); err != nil {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
if d.productIDs.NumEntries > 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
if d.productIDs.NumEntries == 0 {
|
||||
return errTimeout
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnableReport enables a specific sensor report at the given interval.
|
||||
func (d *Device) EnableReport(id SensorID, intervalUs uint32) error {
|
||||
err := d.sh2.enableReport(id, intervalUs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Poll a few times to let the sensor process the command
|
||||
// and potentially send acknowledgment
|
||||
for i := 0; i < 10; i++ {
|
||||
_ = d.service()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSensorConfig retrieves the current configuration for a sensor.
|
||||
func (d *Device) GetSensorConfig(id SensorID) (SensorConfig, error) {
|
||||
return d.sh2.getSensorConfig(id)
|
||||
}
|
||||
|
||||
// SetSensorConfig sets the configuration for a sensor.
|
||||
func (d *Device) SetSensorConfig(id SensorID, config SensorConfig) error {
|
||||
return d.sh2.setSensorConfig(id, config)
|
||||
}
|
||||
|
||||
// WasReset returns true if the sensor signaled a reset since the last call.
|
||||
func (d *Device) WasReset() bool {
|
||||
if d.lastReset {
|
||||
d.lastReset = false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetSensorEvent retrieves the next available sensor event if present.
|
||||
func (d *Device) GetSensorEvent() (SensorValue, bool) {
|
||||
if d.queueCount == 0 {
|
||||
if err := d.service(); err != nil {
|
||||
return SensorValue{}, false
|
||||
}
|
||||
if d.queueCount == 0 {
|
||||
return SensorValue{}, false
|
||||
}
|
||||
}
|
||||
|
||||
value := d.queue[d.queueHead]
|
||||
d.queueHead = (d.queueHead + 1) % len(d.queue)
|
||||
d.queueCount--
|
||||
|
||||
return value, true
|
||||
}
|
||||
|
||||
// ProductIDs returns the cached product identification information.
|
||||
func (d *Device) ProductIDs() ProductIDs {
|
||||
return d.productIDs
|
||||
}
|
||||
|
||||
// Service processes pending sensor data.
|
||||
// This is called automatically by GetSensorEvent but can be called manually
|
||||
// for more control over timing.
|
||||
func (d *Device) Service() error {
|
||||
return d.service()
|
||||
}
|
||||
|
||||
func (d *Device) enqueue(value SensorValue) {
|
||||
next := (d.queueTail + 1) % len(d.queue)
|
||||
if d.queueCount == len(d.queue) {
|
||||
// Queue full, drop oldest
|
||||
d.queueHead = (d.queueHead + 1) % len(d.queue)
|
||||
d.queueCount--
|
||||
}
|
||||
d.queue[d.queueTail] = value
|
||||
d.queueTail = next
|
||||
d.queueCount++
|
||||
}
|
||||
|
||||
func (d *Device) service() error {
|
||||
if d.shtp == nil {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
processed, err := d.shtp.poll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !processed {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) hardwareReset() {
|
||||
if d.resetPin == nil {
|
||||
return
|
||||
}
|
||||
d.resetPin.High()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.resetPin.Low()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
d.resetPin.High()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package bno08x
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// I2CConfig holds I2C-specific configuration options.
|
||||
type I2CConfig struct {
|
||||
// Address is the I2C address (default: 0x4A).
|
||||
Address uint16
|
||||
|
||||
// ResetPin is the optional hardware reset pin.
|
||||
ResetPin pin.OutputFunc
|
||||
|
||||
// ReadChunk is the I2C read chunk size (default: 32 bytes).
|
||||
ReadChunk int
|
||||
}
|
||||
|
||||
const (
|
||||
// DefaultAddress is the default I2C address.
|
||||
DefaultAddress = 0x4A
|
||||
)
|
||||
|
||||
// NewI2C creates a new BNO08x device using I2C communication.
|
||||
func NewI2C(bus drivers.I2C) *Device {
|
||||
return &Device{
|
||||
bus: &I2CBus{
|
||||
wire: bus,
|
||||
address: DefaultAddress,
|
||||
readChunk: i2cDefaultChunk,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// I2CBus implements the Buser interface for I2C communication.
|
||||
type I2CBus struct {
|
||||
wire drivers.I2C
|
||||
address uint16
|
||||
readChunk int
|
||||
scratch []byte
|
||||
header [shtpHeaderLength]byte
|
||||
}
|
||||
|
||||
// configure sets up the I2C bus with the specified address and chunk size.
|
||||
func (b *I2CBus) configure(address uint16, readChunk int) error {
|
||||
if address != 0 {
|
||||
b.address = address
|
||||
}
|
||||
if readChunk > 0 {
|
||||
b.readChunk = readChunk
|
||||
}
|
||||
|
||||
chunk := b.readChunk
|
||||
if chunk < shtpHeaderLength {
|
||||
chunk = shtpHeaderLength
|
||||
}
|
||||
b.scratch = make([]byte, chunk)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// read reads data from the I2C bus.
|
||||
func (b *I2CBus) read(target []byte) (int, uint32, error) {
|
||||
// Read SHTP header (4 bytes) to get packet length
|
||||
// Use pre-allocated header buffer to avoid allocations
|
||||
err := b.wire.Tx(b.address, nil, b.header[:])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// Parse packet length from header
|
||||
packetLen := uint16(b.header[0]) | (uint16(b.header[1]) << 8)
|
||||
|
||||
// Check if continuation bit is set (0x8000)
|
||||
// This means no data is available yet
|
||||
if packetLen&continueMask != 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
// No continuation bit, check for actual data
|
||||
if packetLen == 0 {
|
||||
return 0, 0, nil
|
||||
}
|
||||
|
||||
if int(packetLen) > len(target) {
|
||||
return 0, 0, errBufferTooSmall
|
||||
}
|
||||
|
||||
// Now read the full packet in chunks, re-reading the header in first chunk
|
||||
// This follows Arduino's approach: initial header read is just to get size,
|
||||
// actual packet data (including header) is read in the loop
|
||||
cargoRemaining := int(packetLen)
|
||||
offset := 0
|
||||
firstRead := true
|
||||
|
||||
for cargoRemaining > 0 {
|
||||
var request int
|
||||
if firstRead {
|
||||
// First read: get the full packet including header (up to chunkSize)
|
||||
request = b.readChunk
|
||||
if request > cargoRemaining {
|
||||
request = cargoRemaining
|
||||
}
|
||||
} else {
|
||||
// Subsequent reads: each chunk has a 4-byte header we need to skip
|
||||
request = b.readChunk
|
||||
if request > cargoRemaining+shtpHeaderLength {
|
||||
request = cargoRemaining + shtpHeaderLength
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure scratch buffer is large enough
|
||||
if request > len(b.scratch) {
|
||||
b.scratch = make([]byte, request)
|
||||
}
|
||||
buf := b.scratch[:request]
|
||||
|
||||
// Read chunk
|
||||
err = b.wire.Tx(b.address, nil, buf)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
var cargoRead int
|
||||
if firstRead {
|
||||
// First read: copy everything including header
|
||||
cargoRead = request
|
||||
copy(target[offset:], buf[:cargoRead])
|
||||
firstRead = false
|
||||
} else {
|
||||
// Subsequent reads: skip the 4-byte header
|
||||
cargoRead = request - shtpHeaderLength
|
||||
copy(target[offset:], buf[shtpHeaderLength:shtpHeaderLength+cargoRead])
|
||||
}
|
||||
|
||||
offset += cargoRead
|
||||
cargoRemaining -= cargoRead
|
||||
}
|
||||
|
||||
// Extract timestamp from the header in the target buffer
|
||||
timestamp := uint32(target[2]) | (uint32(target[3]) << 8)
|
||||
|
||||
return int(packetLen), timestamp, nil
|
||||
}
|
||||
|
||||
// write sends data over the I2C bus.
|
||||
func (b *I2CBus) write(data []byte) error {
|
||||
return b.wire.Tx(b.address, data, nil)
|
||||
}
|
||||
|
||||
// softReset sends a soft reset command via I2C.
|
||||
func (b *I2CBus) softReset() error {
|
||||
// Send soft reset packet via I2C as per Adafruit implementation
|
||||
// Format: [length_low, length_high, channel, sequence, command]
|
||||
// This is: 5 bytes total, channel 1 (executable), command 1 (reset)
|
||||
softResetPacket := []byte{5, 0, 1, 0, 1}
|
||||
|
||||
// Try up to 5 times
|
||||
var err error
|
||||
for i := 0; i < 5; i++ {
|
||||
err = b.wire.Tx(b.address, softResetPacket, nil)
|
||||
if err == nil {
|
||||
// Success - wait for sensor to process reset
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
return nil
|
||||
}
|
||||
time.Sleep(30 * time.Millisecond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package bno08x
|
||||
|
||||
// I2C and protocol constants
|
||||
const (
|
||||
shtpHeaderLength = 4
|
||||
maxTransferOut = 256
|
||||
maxTransferIn = 384
|
||||
|
||||
i2cDefaultChunk = 32
|
||||
continueMask = 0x8000
|
||||
)
|
||||
|
||||
// SHTP channel numbers
|
||||
const (
|
||||
channelCommand = 0
|
||||
channelExecutable = 1
|
||||
channelControl = 2
|
||||
channelSensorReport = 3
|
||||
channelWakeReport = 4
|
||||
channelGyroRV = 5
|
||||
)
|
||||
|
||||
// SH-2 report IDs
|
||||
const (
|
||||
reportProdIDReq = 0xF9
|
||||
reportProdIDResp = 0xF8
|
||||
reportSetFeature = 0xFD
|
||||
reportGetFeature = 0xFE
|
||||
reportGetFeatureResp = 0xFC
|
||||
reportCommandReq = 0xF2
|
||||
reportCommandResp = 0xF1
|
||||
reportFRSWriteReq = 0xF7
|
||||
reportFRSWriteData = 0xF6
|
||||
reportFRSReadReq = 0xF4
|
||||
reportFRSReadResp = 0xF3
|
||||
reportBaseTimestamp = 0xFB
|
||||
reportTimestampReuse = 0xFA
|
||||
reportForceFlush = 0xF0
|
||||
reportFlushCompleted = 0xEF
|
||||
reportResetReq = 0xF1
|
||||
reportResetResp = 0xF0
|
||||
)
|
||||
|
||||
// SH-2 commands
|
||||
const (
|
||||
cmdErrors = 0x01
|
||||
cmdCounts = 0x02
|
||||
cmdTare = 0x03
|
||||
cmdInitialize = 0x04
|
||||
cmdFRS = 0x05
|
||||
cmdDCD = 0x06
|
||||
cmdMECal = 0x07
|
||||
cmdProdIDReq = 0x07
|
||||
cmdDCDSave = 0x09
|
||||
cmdGetOscType = 0x0A
|
||||
cmdClearDCDReset = 0x0B
|
||||
cmdCal = 0x0C
|
||||
cmdBootloader = 0x0D
|
||||
cmdInteractiveZRO = 0x0E
|
||||
|
||||
// Command parameters
|
||||
initSystem = 0x01
|
||||
initUnsolicited = 0x80
|
||||
|
||||
countsClearCounts = 0x01
|
||||
countsGetCounts = 0x00
|
||||
|
||||
tareTareNow = 0x00
|
||||
tarePersist = 0x01
|
||||
tareSetReorientation = 0x02
|
||||
|
||||
calStart = 0x00
|
||||
calFinish = 0x01
|
||||
|
||||
commandParamCount = 9
|
||||
responseValueCount = 11
|
||||
)
|
||||
|
||||
// Feature report flags
|
||||
const (
|
||||
featChangeSensitivityRelative = 0x01
|
||||
featChangeSensitivityEnabled = 0x02
|
||||
featWakeEnabled = 0x04
|
||||
featAlwaysOnEnabled = 0x08
|
||||
)
|
||||
|
||||
// Scaling factors for sensor data
|
||||
// These are derived from the Q-point encoding in the SH-2 specification
|
||||
const (
|
||||
scaleQuat = 1.0 / 16384.0 // Q14
|
||||
scaleAccel = 1.0 / 256.0 // Q8
|
||||
scaleGyro = 1.0 / 512.0 // Q9
|
||||
scaleMag = 1.0 / 16.0 // Q4
|
||||
scaleAccuracy = 1.0 / 4096.0 // Q12
|
||||
scalePressure = 1.0 / 1048576.0 // Q20
|
||||
scaleLight = 1.0 / 256.0 // Q8
|
||||
scaleHumidity = 1.0 / 256.0 // Q8
|
||||
scaleProximity = 1.0 / 16.0 // Q4
|
||||
scaleTemperature = 1.0 / 128.0 // Q7
|
||||
scaleAngle = 1.0 / 16.0 // Q4
|
||||
scaleHeartRate = 1.0 / 16.0 // Q4
|
||||
)
|
||||
|
||||
// Activity classifier codes (extended beyond standard SH-2)
|
||||
const (
|
||||
ActivityUnknown = 0
|
||||
ActivityInVehicle = 1
|
||||
ActivityOnBicycle = 2
|
||||
ActivityOnFoot = 3
|
||||
ActivityStill = 4
|
||||
ActivityTilting = 5
|
||||
ActivityWalking = 6
|
||||
ActivityRunning = 7
|
||||
ActivityOnStairs = 8
|
||||
ActivityOptionCount = 9
|
||||
)
|
||||
|
||||
// Stability classifier values
|
||||
const (
|
||||
StabilityUnknown = 0
|
||||
StabilityOnTable = 1
|
||||
StabilityStationary = 2
|
||||
StabilityStable = 3
|
||||
StabilityMotion = 4
|
||||
)
|
||||
|
||||
// Tap detector flags
|
||||
const (
|
||||
TapX = 0x01 // 1 - X axis tapped
|
||||
TapXPos = 0x02 // 2 - X positive direction
|
||||
TapY = 0x04 // 4 - Y axis tapped
|
||||
TapYPos = 0x08 // 8 - Y positive direction
|
||||
TapZ = 0x10 // 16 - Z axis tapped
|
||||
TapZPos = 0x20 // 32 - Z positive direction
|
||||
TapDouble = 0x40 // 64 - Double tap occurred
|
||||
)
|
||||
|
||||
// GUID values for SHTP
|
||||
const (
|
||||
guidSHTP = 0
|
||||
guidExecutable = 1
|
||||
guidSensorHub = 2
|
||||
)
|
||||
|
||||
// Advertisement tags
|
||||
const (
|
||||
tagNull = 0
|
||||
tagGUID = 1
|
||||
tagMaxCargoHeaderWrite = 2
|
||||
tagMaxCargoHeaderRead = 3
|
||||
tagMaxTransferWrite = 4
|
||||
tagMaxTransferRead = 5
|
||||
tagNormalChannel = 6
|
||||
tagWakeChannel = 7
|
||||
tagAppName = 8
|
||||
tagChannelName = 9
|
||||
tagAdvCount = 10
|
||||
tagAppSpecific = 0x80
|
||||
tagSH2Version = 0x80
|
||||
tagSH2ReportLengths = 0x81
|
||||
)
|
||||
|
||||
// Timeouts
|
||||
const (
|
||||
advertTimeout = 200000 // microseconds
|
||||
commandTimeout = 300000 // microseconds
|
||||
)
|
||||
|
||||
// Executable device commands
|
||||
const (
|
||||
execDeviceCmdReset = 1
|
||||
execDeviceCmdOn = 2
|
||||
execDeviceCmdSleep = 3
|
||||
)
|
||||
|
||||
// Executable device responses
|
||||
const (
|
||||
execDeviceRespResetComplete = 1
|
||||
)
|
||||
@@ -0,0 +1,316 @@
|
||||
package bno08x
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// decodeSensor decodes a sensor report payload into a SensorValue.
|
||||
func decodeSensor(payload []byte, timestamp uint32) (SensorValue, bool) {
|
||||
if len(payload) < 4 {
|
||||
return SensorValue{}, false
|
||||
}
|
||||
|
||||
value := SensorValue{
|
||||
id: SensorID(payload[0]),
|
||||
sequence: payload[1],
|
||||
status: payload[2] & 0x03,
|
||||
delay: payload[3],
|
||||
timestamp: uint64(timestamp),
|
||||
}
|
||||
|
||||
data := payload[4:]
|
||||
|
||||
switch value.id {
|
||||
case SensorRawAccelerometer:
|
||||
if len(data) >= 10 {
|
||||
value.rawAccelerometer = RawVector3{
|
||||
X: int16(binary.LittleEndian.Uint16(data[0:])),
|
||||
Y: int16(binary.LittleEndian.Uint16(data[2:])),
|
||||
Z: int16(binary.LittleEndian.Uint16(data[4:])),
|
||||
Timestamp: binary.LittleEndian.Uint32(data[6:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorAccelerometer:
|
||||
if len(data) >= 6 {
|
||||
value.accelerometer = Vector3{
|
||||
X: qToFloat(data[0:], scaleAccel),
|
||||
Y: qToFloat(data[2:], scaleAccel),
|
||||
Z: qToFloat(data[4:], scaleAccel),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorLinearAcceleration:
|
||||
if len(data) >= 6 {
|
||||
value.linearAcceleration = Vector3{
|
||||
X: qToFloat(data[0:], scaleAccel),
|
||||
Y: qToFloat(data[2:], scaleAccel),
|
||||
Z: qToFloat(data[4:], scaleAccel),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGravity:
|
||||
if len(data) >= 6 {
|
||||
value.gravity = Vector3{
|
||||
X: qToFloat(data[0:], scaleAccel),
|
||||
Y: qToFloat(data[2:], scaleAccel),
|
||||
Z: qToFloat(data[4:], scaleAccel),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorRawGyroscope:
|
||||
if len(data) >= 12 {
|
||||
value.rawGyroscope = RawGyroscope{
|
||||
X: int16(binary.LittleEndian.Uint16(data[0:])),
|
||||
Y: int16(binary.LittleEndian.Uint16(data[2:])),
|
||||
Z: int16(binary.LittleEndian.Uint16(data[4:])),
|
||||
Temperature: int16(binary.LittleEndian.Uint16(data[6:])),
|
||||
Timestamp: binary.LittleEndian.Uint32(data[8:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGyroscope:
|
||||
if len(data) >= 6 {
|
||||
value.gyroscope = Vector3{
|
||||
X: qToFloat(data[0:], scaleGyro),
|
||||
Y: qToFloat(data[2:], scaleGyro),
|
||||
Z: qToFloat(data[4:], scaleGyro),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGyroscopeUncalibrated:
|
||||
if len(data) >= 12 {
|
||||
value.gyroscopeUncal = GyroscopeUncalibrated{
|
||||
X: qToFloat(data[0:], scaleGyro),
|
||||
Y: qToFloat(data[2:], scaleGyro),
|
||||
Z: qToFloat(data[4:], scaleGyro),
|
||||
BiasX: qToFloat(data[6:], scaleGyro),
|
||||
BiasY: qToFloat(data[8:], scaleGyro),
|
||||
BiasZ: qToFloat(data[10:], scaleGyro),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorRawMagnetometer:
|
||||
if len(data) >= 10 {
|
||||
value.rawMagnetometer = RawVector3{
|
||||
X: int16(binary.LittleEndian.Uint16(data[0:])),
|
||||
Y: int16(binary.LittleEndian.Uint16(data[2:])),
|
||||
Z: int16(binary.LittleEndian.Uint16(data[4:])),
|
||||
Timestamp: binary.LittleEndian.Uint32(data[6:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorMagneticField:
|
||||
if len(data) >= 6 {
|
||||
value.magneticField = Vector3{
|
||||
X: qToFloat(data[0:], scaleMag),
|
||||
Y: qToFloat(data[2:], scaleMag),
|
||||
Z: qToFloat(data[4:], scaleMag),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorMagneticFieldUncalibrated:
|
||||
if len(data) >= 12 {
|
||||
value.magneticFieldUncal = MagneticFieldUncalibrated{
|
||||
X: qToFloat(data[0:], scaleMag),
|
||||
Y: qToFloat(data[2:], scaleMag),
|
||||
Z: qToFloat(data[4:], scaleMag),
|
||||
BiasX: qToFloat(data[6:], scaleMag),
|
||||
BiasY: qToFloat(data[8:], scaleMag),
|
||||
BiasZ: qToFloat(data[10:], scaleMag),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorRotationVector:
|
||||
if len(data) >= 10 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
|
||||
}
|
||||
|
||||
case SensorGameRotationVector:
|
||||
if len(data) >= 8 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGeomagneticRotationVector:
|
||||
if len(data) >= 10 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
|
||||
}
|
||||
|
||||
case SensorARVRStabilizedRV:
|
||||
if len(data) >= 10 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
value.quaternionAccuracy = qToFloat(data[8:], scaleAccuracy)
|
||||
}
|
||||
|
||||
case SensorARVRStabilizedGRV:
|
||||
if len(data) >= 8 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorGyroIntegratedRV:
|
||||
if len(data) >= 10 {
|
||||
value.quaternion = Quaternion{
|
||||
I: qToFloat(data[0:], scaleQuat),
|
||||
J: qToFloat(data[2:], scaleQuat),
|
||||
K: qToFloat(data[4:], scaleQuat),
|
||||
Real: qToFloat(data[6:], scaleQuat),
|
||||
}
|
||||
// Angular velocity X at data[8:10]
|
||||
}
|
||||
|
||||
case SensorPressure:
|
||||
if len(data) >= 4 {
|
||||
value.pressure = float32(int32(binary.LittleEndian.Uint32(data[0:]))) * scalePressure
|
||||
}
|
||||
|
||||
case SensorAmbientLight:
|
||||
if len(data) >= 4 {
|
||||
value.ambientLight = float32(int32(binary.LittleEndian.Uint32(data[0:]))) * scaleLight
|
||||
}
|
||||
|
||||
case SensorHumidity:
|
||||
if len(data) >= 2 {
|
||||
value.humidity = qToFloat(data[0:], scaleHumidity)
|
||||
}
|
||||
|
||||
case SensorProximity:
|
||||
if len(data) >= 2 {
|
||||
value.proximity = qToFloat(data[0:], scaleProximity)
|
||||
}
|
||||
|
||||
case SensorTemperature:
|
||||
if len(data) >= 2 {
|
||||
value.temperature = qToFloat(data[0:], scaleTemperature)
|
||||
}
|
||||
|
||||
case SensorTapDetector:
|
||||
if len(data) >= 1 {
|
||||
value.tapDetector = TapDetector{
|
||||
Flags: data[0],
|
||||
}
|
||||
}
|
||||
|
||||
case SensorStepDetector:
|
||||
if len(data) >= 4 {
|
||||
value.stepDetector = StepDetector{
|
||||
Latency: binary.LittleEndian.Uint32(data[0:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorStepCounter:
|
||||
if len(data) >= 8 {
|
||||
value.stepCounter = StepCounter{
|
||||
Count: uint16(binary.LittleEndian.Uint32(data[4:8])),
|
||||
Latency: binary.LittleEndian.Uint32(data[0:4]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorSignificantMotion:
|
||||
if len(data) >= 2 {
|
||||
value.significantMotion = SignificantMotion{
|
||||
Motion: binary.LittleEndian.Uint16(data[0:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorStabilityClassifier:
|
||||
if len(data) >= 1 {
|
||||
value.stabilityClassifier = StabilityClassifier{
|
||||
Classification: data[0],
|
||||
}
|
||||
}
|
||||
|
||||
case SensorStabilityDetector:
|
||||
if len(data) >= 1 {
|
||||
value.stabilityDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorShakeDetector:
|
||||
if len(data) >= 2 {
|
||||
value.shakeDetector = ShakeDetector{
|
||||
Shake: binary.LittleEndian.Uint16(data[0:]),
|
||||
}
|
||||
}
|
||||
|
||||
case SensorFlipDetector:
|
||||
if len(data) >= 2 {
|
||||
value.flipDetector = binary.LittleEndian.Uint16(data[0:2])
|
||||
}
|
||||
|
||||
case SensorPickupDetector:
|
||||
if len(data) >= 2 {
|
||||
// Pickup detected at data[0:2]
|
||||
}
|
||||
|
||||
case SensorPersonalActivityClassifier:
|
||||
if len(data) >= 16 {
|
||||
value.personalActivityClassifier = PersonalActivityClassifier{
|
||||
Page: data[0],
|
||||
MostLikelyState: data[1],
|
||||
EndOfPage: data[15],
|
||||
}
|
||||
for i := 0; i < 10 && i+2 < len(data); i++ {
|
||||
value.personalActivityClassifier.Confidence[i] = data[2+i]
|
||||
}
|
||||
}
|
||||
|
||||
case SensorSleepDetector:
|
||||
if len(data) >= 1 {
|
||||
value.sleepDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorTiltDetector:
|
||||
if len(data) >= 1 {
|
||||
value.tiltDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorPocketDetector:
|
||||
if len(data) >= 1 {
|
||||
value.pocketDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorCircleDetector:
|
||||
if len(data) >= 1 {
|
||||
value.circleDetector = data[0]
|
||||
}
|
||||
|
||||
case SensorHeartRateMonitor:
|
||||
if len(data) >= 2 {
|
||||
value.heartRateMonitor = binary.LittleEndian.Uint16(data[0:])
|
||||
}
|
||||
}
|
||||
|
||||
return value, true
|
||||
}
|
||||
|
||||
// qToFloat converts a Q-point fixed-point value to float32.
|
||||
func qToFloat(data []byte, scale float32) float32 {
|
||||
if len(data) < 2 {
|
||||
return 0
|
||||
}
|
||||
return float32(int16(binary.LittleEndian.Uint16(data))) * scale
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package bno08x
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// hal implements the hardware abstraction layer for bus communication.
|
||||
type hal struct {
|
||||
device *Device
|
||||
}
|
||||
|
||||
func newHAL(dev *Device) *hal {
|
||||
return &hal{
|
||||
device: dev,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hal) open() error {
|
||||
// HAL is now open and ready for communication
|
||||
// Soft reset will be sent after handlers are registered
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *hal) close() {}
|
||||
|
||||
func (h *hal) read(target []byte) (int, uint32, error) {
|
||||
return h.device.bus.read(target)
|
||||
}
|
||||
|
||||
func (h *hal) write(frame []byte) (int, error) {
|
||||
if len(frame) > maxTransferOut {
|
||||
return 0, errFrameTooLarge
|
||||
}
|
||||
err := h.device.bus.write(frame)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(frame), nil
|
||||
}
|
||||
|
||||
func (h *hal) getTimeUs() uint32 {
|
||||
return uint32(time.Now().UnixNano() / 1000)
|
||||
}
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
// SH-2 specification found at https://www.ceva-ip.com/wp-content/uploads/SH-2-Reference-Manual.pdf
|
||||
|
||||
package bno08x
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
// getReportLen returns the length in bytes of a sensor report given its ID.
|
||||
// Returns 0 for unknown report IDs.
|
||||
func getReportLen(reportID byte) int {
|
||||
switch reportID {
|
||||
case 0xF1: // FLUSH_COMPLETED
|
||||
return 6
|
||||
case 0xFA: // TIMESTAMP_REBASE
|
||||
return 5
|
||||
case 0xFB: // BASE_TIMESTAMP_REF
|
||||
return 5
|
||||
case 0xFC: // GET_FEATURE_RESP
|
||||
return 17
|
||||
case 0x01: // Accelerometer (calibrated)
|
||||
return 10
|
||||
case 0x02: // Gyroscope (calibrated)
|
||||
return 10
|
||||
case 0x03: // Magnetic field (calibrated)
|
||||
return 10
|
||||
case 0x04: // Linear acceleration
|
||||
return 10
|
||||
case 0x05: // Rotation vector
|
||||
return 14
|
||||
case 0x06: // Gravity
|
||||
return 10
|
||||
case 0x07: // Gyroscope uncalibrated
|
||||
return 16
|
||||
case 0x08: // Game rotation vector
|
||||
return 12
|
||||
case 0x09: // Geomagnetic rotation vector
|
||||
return 14
|
||||
case 0x0A: // Pressure
|
||||
return 10
|
||||
case 0x0B: // Ambient light
|
||||
return 10
|
||||
case 0x0C: // Humidity
|
||||
return 10
|
||||
case 0x0D: // Proximity
|
||||
return 10
|
||||
case 0x0E: // Temperature
|
||||
return 10
|
||||
case 0x0F: // Magnetic field uncalibrated
|
||||
return 16
|
||||
case 0x10: // Tap detector
|
||||
return 5
|
||||
case 0x11: // Step counter
|
||||
return 12
|
||||
case 0x12: // Significant motion
|
||||
return 6
|
||||
case 0x13: // Stability classifier
|
||||
return 5
|
||||
case 0x14: // Raw accelerometer
|
||||
return 16
|
||||
case 0x15: // Raw gyroscope
|
||||
return 16
|
||||
case 0x16: // Raw magnetometer
|
||||
return 16
|
||||
case 0x18: // Step detector
|
||||
return 8
|
||||
case 0x19: // Shake detector
|
||||
return 6
|
||||
case 0x1A: // Flip detector
|
||||
return 6
|
||||
case 0x1B: // Pickup detector
|
||||
return 6
|
||||
case 0x1C: // Stability detector
|
||||
return 6
|
||||
case 0x1E: // Personal activity classifier
|
||||
return 16
|
||||
default:
|
||||
// For most sensor reports, they are typically 10-16 bytes
|
||||
// If we don't know the exact length, return a safe default
|
||||
// that covers most cases (the handler will bounds-check)
|
||||
if reportID < 0xF0 {
|
||||
return 10 // Most sensor reports are at least this long
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// sh2Protocol implements the Sensor Hub 2 (SH-2) application protocol.
|
||||
type sh2Protocol struct {
|
||||
device *Device
|
||||
transport *shtp
|
||||
cmdSeq uint8
|
||||
waiting bool
|
||||
lastCmd uint8
|
||||
pendingConfigRequest bool
|
||||
pendingConfigSensor SensorID
|
||||
receivedConfig SensorConfig
|
||||
configReady bool
|
||||
configBuf [17]byte // Reusable buffer for setSensorConfig
|
||||
commandBuf [3 + commandParamCount]byte // Reusable buffer for sendCommand
|
||||
}
|
||||
|
||||
func newSH2Protocol(device *Device) *sh2Protocol {
|
||||
proto := &sh2Protocol{
|
||||
device: device,
|
||||
transport: device.shtp,
|
||||
}
|
||||
|
||||
// Register handlers for each channel
|
||||
device.shtp.register(channelControl, proto.handleControl)
|
||||
device.shtp.register(channelSensorReport, proto.handleSensor)
|
||||
device.shtp.register(channelWakeReport, proto.handleSensor)
|
||||
device.shtp.register(channelGyroRV, proto.handleSensor)
|
||||
device.shtp.register(channelExecutable, proto.handleExecutable)
|
||||
|
||||
return proto
|
||||
}
|
||||
|
||||
// softReset sends a software reset command to the sensor.
|
||||
func (s *sh2Protocol) softReset() error {
|
||||
payload := []byte{execDeviceCmdReset}
|
||||
return s.transport.send(channelExecutable, payload)
|
||||
}
|
||||
|
||||
// initialize sends the initialize command to the sensor.
|
||||
func (s *sh2Protocol) initialize() error {
|
||||
return s.sendCommand(cmdInitialize, []byte{initSystem})
|
||||
}
|
||||
|
||||
// requestProductIDs requests product identification information.
|
||||
func (s *sh2Protocol) requestProductIDs() error {
|
||||
payload := []byte{reportProdIDReq, 0x00}
|
||||
return s.transport.send(channelControl, payload)
|
||||
}
|
||||
|
||||
// enableReport enables a sensor report at the specified interval.
|
||||
func (s *sh2Protocol) enableReport(id SensorID, intervalUs uint32) error {
|
||||
config := SensorConfig{
|
||||
ReportInterval: intervalUs,
|
||||
}
|
||||
return s.setSensorConfig(id, config)
|
||||
}
|
||||
|
||||
// getSensorConfig retrieves the configuration for a sensor.
|
||||
// This method sends a GET_FEATURE request and waits for the response
|
||||
// by polling the device. It will timeout after approximately 1 second.
|
||||
func (s *sh2Protocol) getSensorConfig(id SensorID) (SensorConfig, error) {
|
||||
// Mark that we're waiting for a config response
|
||||
s.pendingConfigRequest = true
|
||||
s.pendingConfigSensor = id
|
||||
s.configReady = false
|
||||
|
||||
payload := []byte{reportGetFeature, byte(id)}
|
||||
err := s.transport.send(channelControl, payload)
|
||||
if err != nil {
|
||||
s.pendingConfigRequest = false
|
||||
return SensorConfig{}, err
|
||||
}
|
||||
|
||||
// Poll for response with timeout
|
||||
maxAttempts := 100 // ~1 second with 10ms delays
|
||||
for i := 0; i < maxAttempts; i++ {
|
||||
// Service the device to process incoming messages
|
||||
s.device.shtp.poll()
|
||||
|
||||
if s.configReady {
|
||||
s.pendingConfigRequest = false
|
||||
s.configReady = false
|
||||
return s.receivedConfig, nil
|
||||
}
|
||||
|
||||
// Small delay between polls
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
s.pendingConfigRequest = false
|
||||
return SensorConfig{}, errTimeout
|
||||
}
|
||||
|
||||
// setSensorConfig configures a sensor.
|
||||
func (s *sh2Protocol) setSensorConfig(id SensorID, config SensorConfig) error {
|
||||
// Use pre-allocated buffer to avoid allocations
|
||||
payload := s.configBuf[:]
|
||||
payload[0] = reportSetFeature
|
||||
payload[1] = byte(id)
|
||||
|
||||
// Build feature flags
|
||||
var flags uint8
|
||||
if config.ChangeSensitivityEnabled {
|
||||
flags |= featChangeSensitivityEnabled
|
||||
}
|
||||
if config.ChangeSensitivityRelative {
|
||||
flags |= featChangeSensitivityRelative
|
||||
}
|
||||
if config.WakeupEnabled {
|
||||
flags |= featWakeEnabled
|
||||
}
|
||||
if config.AlwaysOnEnabled {
|
||||
flags |= featAlwaysOnEnabled
|
||||
}
|
||||
payload[2] = flags
|
||||
|
||||
binary.LittleEndian.PutUint16(payload[3:5], config.ChangeSensitivity)
|
||||
binary.LittleEndian.PutUint32(payload[5:9], config.ReportInterval)
|
||||
binary.LittleEndian.PutUint32(payload[9:13], config.BatchInterval)
|
||||
binary.LittleEndian.PutUint32(payload[13:17], config.SensorSpecific)
|
||||
|
||||
return s.transport.send(channelControl, payload)
|
||||
}
|
||||
|
||||
// sendCommand sends a command with parameters to the sensor.
|
||||
func (s *sh2Protocol) sendCommand(command byte, params []byte) error {
|
||||
// Use pre-allocated buffer to avoid allocations
|
||||
payload := s.commandBuf[:]
|
||||
payload[0] = reportCommandReq
|
||||
payload[1] = s.cmdSeq
|
||||
payload[2] = command
|
||||
s.cmdSeq++
|
||||
s.lastCmd = command
|
||||
s.waiting = true
|
||||
|
||||
for i := 0; i < commandParamCount && i < len(params); i++ {
|
||||
payload[3+i] = params[i]
|
||||
}
|
||||
|
||||
return s.transport.send(channelControl, payload[:3+commandParamCount])
|
||||
}
|
||||
|
||||
// handleControl processes control channel messages.
|
||||
func (s *sh2Protocol) handleControl(payload []byte, timestamp uint32) {
|
||||
if len(payload) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
reportID := payload[0]
|
||||
|
||||
switch reportID {
|
||||
case reportProdIDResp:
|
||||
s.handleProdID(payload, timestamp)
|
||||
case reportCommandResp:
|
||||
s.handleCommandResp(payload, timestamp)
|
||||
case reportGetFeatureResp:
|
||||
s.handleGetFeatureResp(payload, timestamp)
|
||||
case reportFRSReadResp:
|
||||
// FRS (Flash Record System) read response
|
||||
// Not implemented in basic version
|
||||
}
|
||||
}
|
||||
|
||||
// handleProdID processes product ID responses.
|
||||
func (s *sh2Protocol) handleProdID(payload []byte, timestamp uint32) {
|
||||
if len(payload) < 16 {
|
||||
return
|
||||
}
|
||||
|
||||
entry := ProductID{
|
||||
ResetCause: payload[1],
|
||||
VersionMajor: payload[2],
|
||||
VersionMinor: payload[3],
|
||||
PartNumber: binary.LittleEndian.Uint32(payload[4:8]),
|
||||
BuildNumber: binary.LittleEndian.Uint32(payload[8:12]),
|
||||
VersionPatch: binary.LittleEndian.Uint16(payload[12:14]),
|
||||
Reserved0: payload[14],
|
||||
Reserved1: payload[15],
|
||||
}
|
||||
|
||||
// Store in first slot
|
||||
s.device.productIDs.Entries[0] = entry
|
||||
s.device.productIDs.NumEntries = 1
|
||||
}
|
||||
|
||||
// handleCommandResp processes command responses.
|
||||
func (s *sh2Protocol) handleCommandResp(payload []byte, timestamp uint32) {
|
||||
if len(payload) < 16 {
|
||||
return
|
||||
}
|
||||
|
||||
// seq := payload[1]
|
||||
command := payload[2]
|
||||
// commandSeq := payload[3]
|
||||
// respSeq := payload[4]
|
||||
|
||||
// Check if this response is for our command
|
||||
if s.waiting && command == s.lastCmd {
|
||||
s.waiting = false
|
||||
// Status is in payload[6]
|
||||
// For now, we just acknowledge receipt
|
||||
}
|
||||
}
|
||||
|
||||
// handleGetFeatureResp processes get feature responses.
|
||||
func (s *sh2Protocol) handleGetFeatureResp(payload []byte, timestamp uint32) {
|
||||
if len(payload) < 17 {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the response
|
||||
sensorID := SensorID(payload[1])
|
||||
flags := payload[2]
|
||||
changeSensitivity := binary.LittleEndian.Uint16(payload[3:5])
|
||||
reportInterval := binary.LittleEndian.Uint32(payload[5:9])
|
||||
batchInterval := binary.LittleEndian.Uint32(payload[9:13])
|
||||
sensorSpecific := binary.LittleEndian.Uint32(payload[13:17])
|
||||
|
||||
// If we're waiting for this sensor's config, store it
|
||||
if s.pendingConfigRequest && s.pendingConfigSensor == sensorID {
|
||||
s.receivedConfig = SensorConfig{
|
||||
ChangeSensitivityEnabled: flags&featChangeSensitivityEnabled != 0,
|
||||
ChangeSensitivityRelative: flags&featChangeSensitivityRelative != 0,
|
||||
WakeupEnabled: flags&featWakeEnabled != 0,
|
||||
AlwaysOnEnabled: flags&featAlwaysOnEnabled != 0,
|
||||
ChangeSensitivity: changeSensitivity,
|
||||
ReportInterval: reportInterval,
|
||||
BatchInterval: batchInterval,
|
||||
SensorSpecific: sensorSpecific,
|
||||
}
|
||||
s.configReady = true
|
||||
}
|
||||
}
|
||||
|
||||
// handleSensor processes sensor report messages.
|
||||
// The payload can contain multiple sensor reports batched together.
|
||||
func (s *sh2Protocol) handleSensor(payload []byte, timestamp uint32) {
|
||||
cursor := 0
|
||||
var referenceDelta uint32
|
||||
|
||||
for cursor < len(payload) {
|
||||
if cursor >= len(payload) {
|
||||
break
|
||||
}
|
||||
|
||||
reportID := payload[cursor]
|
||||
reportLen := getReportLen(reportID)
|
||||
|
||||
if reportLen == 0 {
|
||||
// Unknown report ID
|
||||
break
|
||||
}
|
||||
|
||||
if cursor+reportLen > len(payload) {
|
||||
// Not enough data for this report
|
||||
break
|
||||
}
|
||||
|
||||
// Handle special report types
|
||||
switch reportID {
|
||||
case 0xFB: // SENSORHUB_BASE_TIMESTAMP_REF
|
||||
if reportLen >= 5 {
|
||||
// Extract timebase (little-endian uint32)
|
||||
timebase := binary.LittleEndian.Uint32(payload[cursor+1 : cursor+5])
|
||||
referenceDelta = -timebase // Store negative for delta calculation
|
||||
}
|
||||
|
||||
case 0xFA: // SENSORHUB_TIMESTAMP_REBASE
|
||||
if reportLen >= 5 {
|
||||
timebase := binary.LittleEndian.Uint32(payload[cursor+1 : cursor+5])
|
||||
referenceDelta += timebase
|
||||
}
|
||||
|
||||
case 0xF1: // SENSORHUB_FLUSH_COMPLETED
|
||||
// Route to control handler
|
||||
s.handleControl(payload[cursor:cursor+reportLen], timestamp)
|
||||
|
||||
default:
|
||||
// Regular sensor report
|
||||
value, ok := decodeSensor(payload[cursor:cursor+reportLen], timestamp)
|
||||
if ok {
|
||||
s.device.enqueue(value)
|
||||
}
|
||||
}
|
||||
|
||||
cursor += reportLen
|
||||
}
|
||||
} // handleExecutable processes executable channel messages.
|
||||
func (s *sh2Protocol) handleExecutable(payload []byte, timestamp uint32) {
|
||||
if len(payload) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
reportID := payload[0]
|
||||
|
||||
switch reportID {
|
||||
case execDeviceRespResetComplete:
|
||||
s.device.lastReset = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// SHTP specification found at https://www.ceva-ip.com/wp-content/uploads/SH-2-SHTP-Reference-Manual.pdf
|
||||
|
||||
package bno08x
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// shtpHandler is a callback for handling SHTP channel data.
|
||||
type shtpHandler func(payload []byte, timestamp uint32)
|
||||
|
||||
// shtp implements the Sensor Hub Transport Protocol layer.
|
||||
type shtp struct {
|
||||
hal *hal
|
||||
handlers map[uint8]shtpHandler
|
||||
seq [8]uint8
|
||||
rx [maxTransferIn]byte // Reusable receive buffer
|
||||
tx [maxTransferOut]byte // Reusable transmit buffer
|
||||
}
|
||||
|
||||
func newSHTP(hal *hal) *shtp {
|
||||
return &shtp{
|
||||
hal: hal,
|
||||
handlers: make(map[uint8]shtpHandler),
|
||||
}
|
||||
}
|
||||
|
||||
// register registers a handler for a specific SHTP channel.
|
||||
func (s *shtp) register(channel uint8, handler shtpHandler) {
|
||||
if handler == nil {
|
||||
delete(s.handlers, channel)
|
||||
return
|
||||
}
|
||||
s.handlers[channel] = handler
|
||||
}
|
||||
|
||||
// send transmits a payload on the specified channel.
|
||||
func (s *shtp) send(channel uint8, payload []byte) error {
|
||||
total := len(payload) + shtpHeaderLength
|
||||
if total > maxTransferOut {
|
||||
return errFrameTooLarge
|
||||
}
|
||||
|
||||
// Use pre-allocated transmit buffer to avoid allocations
|
||||
frame := s.tx[:total]
|
||||
binary.LittleEndian.PutUint16(frame[0:2], uint16(total))
|
||||
frame[2] = channel
|
||||
frame[3] = s.seq[channel]
|
||||
s.seq[channel]++
|
||||
copy(frame[shtpHeaderLength:], payload)
|
||||
|
||||
_, err := s.hal.write(frame)
|
||||
return err
|
||||
}
|
||||
|
||||
// poll checks for and processes incoming SHTP packets.
|
||||
// Returns true if a packet was processed, false if no data available.
|
||||
func (s *shtp) poll() (bool, error) {
|
||||
n, timestamp, err := s.hal.read(s.rx[:])
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
packet := s.rx[:n]
|
||||
length := int(binary.LittleEndian.Uint16(packet[0:2]) & ^uint16(continueMask))
|
||||
if length > n {
|
||||
length = n
|
||||
}
|
||||
if length < shtpHeaderLength {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
channel := packet[2]
|
||||
// seq := packet[3] // sequence number, not currently validated
|
||||
payload := packet[shtpHeaderLength:length]
|
||||
|
||||
if handler := s.handlers[channel]; handler != nil {
|
||||
handler(payload, timestamp)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
+572
@@ -0,0 +1,572 @@
|
||||
package bno08x
|
||||
|
||||
// SensorID identifies a specific sensor type.
|
||||
type SensorID uint8
|
||||
|
||||
// Sensor IDs as defined in the SH-2 specification.
|
||||
const (
|
||||
SensorRawAccelerometer SensorID = 0x14
|
||||
SensorAccelerometer SensorID = 0x01
|
||||
SensorLinearAcceleration SensorID = 0x04
|
||||
SensorGravity SensorID = 0x06
|
||||
SensorRawGyroscope SensorID = 0x15
|
||||
SensorGyroscope SensorID = 0x02
|
||||
SensorGyroscopeUncalibrated SensorID = 0x07
|
||||
SensorRawMagnetometer SensorID = 0x16
|
||||
SensorMagneticField SensorID = 0x03
|
||||
SensorMagneticFieldUncalibrated SensorID = 0x0F
|
||||
SensorRotationVector SensorID = 0x05
|
||||
SensorGameRotationVector SensorID = 0x08
|
||||
SensorGeomagneticRotationVector SensorID = 0x09
|
||||
SensorPressure SensorID = 0x0A
|
||||
SensorAmbientLight SensorID = 0x0B
|
||||
SensorHumidity SensorID = 0x0C
|
||||
SensorProximity SensorID = 0x0D
|
||||
SensorTemperature SensorID = 0x0E
|
||||
SensorReserved SensorID = 0x17
|
||||
SensorTapDetector SensorID = 0x10
|
||||
SensorStepDetector SensorID = 0x18
|
||||
SensorStepCounter SensorID = 0x11
|
||||
SensorSignificantMotion SensorID = 0x12
|
||||
SensorStabilityClassifier SensorID = 0x13
|
||||
SensorShakeDetector SensorID = 0x19
|
||||
SensorFlipDetector SensorID = 0x1A
|
||||
SensorPickupDetector SensorID = 0x1B
|
||||
SensorStabilityDetector SensorID = 0x1C
|
||||
SensorPersonalActivityClassifier SensorID = 0x1E
|
||||
SensorSleepDetector SensorID = 0x1F
|
||||
SensorTiltDetector SensorID = 0x20
|
||||
SensorPocketDetector SensorID = 0x21
|
||||
SensorCircleDetector SensorID = 0x22
|
||||
SensorHeartRateMonitor SensorID = 0x23
|
||||
SensorARVRStabilizedRV SensorID = 0x28
|
||||
SensorARVRStabilizedGRV SensorID = 0x29
|
||||
SensorGyroIntegratedRV SensorID = 0x2A
|
||||
SensorIZROMotionRequest SensorID = 0x2B
|
||||
SensorMaxID SensorID = 0x2B
|
||||
)
|
||||
|
||||
// ProductID contains firmware information from the sensor.
|
||||
type ProductID struct {
|
||||
ResetCause uint8
|
||||
VersionMajor uint8
|
||||
VersionMinor uint8
|
||||
PartNumber uint32
|
||||
BuildNumber uint32
|
||||
VersionPatch uint16
|
||||
Reserved0 uint8
|
||||
Reserved1 uint8
|
||||
}
|
||||
|
||||
// ProductIDs holds all product ID entries returned by the sensor.
|
||||
type ProductIDs struct {
|
||||
Entries [5]ProductID
|
||||
NumEntries uint8
|
||||
}
|
||||
|
||||
// Vector3 represents a 3D vector.
|
||||
type Vector3 struct {
|
||||
X float32
|
||||
Y float32
|
||||
Z float32
|
||||
}
|
||||
|
||||
// Quaternion represents a quaternion in (real, i, j, k) format.
|
||||
// Note: This maps to (w, x, y, z) convention where w=real, x=i, y=j, z=k.
|
||||
type Quaternion struct {
|
||||
Real float32
|
||||
I float32
|
||||
J float32
|
||||
K float32
|
||||
}
|
||||
|
||||
// RawVector3 contains raw ADC counts with timestamp.
|
||||
type RawVector3 struct {
|
||||
X int16
|
||||
Y int16
|
||||
Z int16
|
||||
Timestamp uint32
|
||||
}
|
||||
|
||||
// RawGyroscope contains raw gyro readings with temperature and timestamp.
|
||||
type RawGyroscope struct {
|
||||
X int16
|
||||
Y int16
|
||||
Z int16
|
||||
Temperature int16
|
||||
Timestamp uint32
|
||||
}
|
||||
|
||||
// GyroscopeUncalibrated contains uncalibrated gyroscope data with bias.
|
||||
type GyroscopeUncalibrated struct {
|
||||
X float32
|
||||
Y float32
|
||||
Z float32
|
||||
BiasX float32
|
||||
BiasY float32
|
||||
BiasZ float32
|
||||
}
|
||||
|
||||
// MagneticFieldUncalibrated contains uncalibrated magnetometer data with bias.
|
||||
type MagneticFieldUncalibrated struct {
|
||||
X float32
|
||||
Y float32
|
||||
Z float32
|
||||
BiasX float32
|
||||
BiasY float32
|
||||
BiasZ float32
|
||||
}
|
||||
|
||||
// TapDetector contains tap/double-tap detection flags.
|
||||
type TapDetector struct {
|
||||
Flags uint8
|
||||
}
|
||||
|
||||
// StepDetector contains step detection with latency.
|
||||
type StepDetector struct {
|
||||
Latency uint32
|
||||
}
|
||||
|
||||
// StepCounter contains step count with latency.
|
||||
type StepCounter struct {
|
||||
Count uint16
|
||||
Latency uint32
|
||||
}
|
||||
|
||||
// SignificantMotion indicates significant motion was detected.
|
||||
type SignificantMotion struct {
|
||||
Motion uint16
|
||||
}
|
||||
|
||||
// ActivityClassification contains activity classification data.
|
||||
type ActivityClassification struct {
|
||||
Page uint8
|
||||
MostLikelyState uint8
|
||||
Classification [10]uint8
|
||||
EndOfPage uint8
|
||||
}
|
||||
|
||||
// ShakeDetector contains shake detection data.
|
||||
type ShakeDetector struct {
|
||||
Shake uint16
|
||||
}
|
||||
|
||||
// StabilityClassifier contains stability classification.
|
||||
type StabilityClassifier struct {
|
||||
Classification uint8
|
||||
}
|
||||
|
||||
// PersonalActivityClassifier contains personal activity data.
|
||||
type PersonalActivityClassifier struct {
|
||||
Page uint8
|
||||
MostLikelyState uint8
|
||||
Confidence [10]uint8
|
||||
EndOfPage uint8
|
||||
}
|
||||
|
||||
// SensorValue contains decoded sensor data for all sensor types.
|
||||
type SensorValue struct {
|
||||
id SensorID
|
||||
status uint8
|
||||
sequence uint8
|
||||
delay uint8
|
||||
timestamp uint64
|
||||
|
||||
// Orientation data (quaternions)
|
||||
quaternion Quaternion
|
||||
quaternionAccuracy float32
|
||||
|
||||
// Linear measurements
|
||||
accelerometer Vector3
|
||||
linearAcceleration Vector3
|
||||
gravity Vector3
|
||||
gyroscope Vector3
|
||||
gyroscopeUncal GyroscopeUncalibrated
|
||||
magneticField Vector3
|
||||
magneticFieldUncal MagneticFieldUncalibrated
|
||||
|
||||
// Raw sensor data
|
||||
rawAccelerometer RawVector3
|
||||
rawGyroscope RawGyroscope
|
||||
rawMagnetometer RawVector3
|
||||
|
||||
// Environmental sensors
|
||||
pressure float32 // hPa
|
||||
ambientLight float32 // lux
|
||||
humidity float32 // %
|
||||
proximity float32 // cm
|
||||
temperature float32 // °C
|
||||
|
||||
// Activity detection
|
||||
tapDetector TapDetector
|
||||
stepCounter StepCounter
|
||||
stepDetector StepDetector
|
||||
significantMotion SignificantMotion
|
||||
shakeDetector ShakeDetector
|
||||
flipDetector uint16
|
||||
stabilityClassifier StabilityClassifier
|
||||
stabilityDetector uint8
|
||||
activityClassifier ActivityClassification
|
||||
personalActivityClassifier PersonalActivityClassifier
|
||||
sleepDetector uint8
|
||||
tiltDetector uint8
|
||||
pocketDetector uint8
|
||||
circleDetector uint8
|
||||
heartRateMonitor uint16
|
||||
}
|
||||
|
||||
// SensorConfig holds configuration settings for a sensor.
|
||||
type SensorConfig struct {
|
||||
ChangeSensitivityEnabled bool
|
||||
ChangeSensitivityRelative bool
|
||||
WakeupEnabled bool
|
||||
AlwaysOnEnabled bool
|
||||
ChangeSensitivity uint16
|
||||
ReportInterval uint32 // microseconds
|
||||
BatchInterval uint32 // microseconds
|
||||
SensorSpecific uint32
|
||||
}
|
||||
|
||||
// Error represents a driver error.
|
||||
type Error string
|
||||
|
||||
func (e Error) Error() string { return string(e) }
|
||||
|
||||
// Error constants.
|
||||
var (
|
||||
errBufferTooSmall = Error("bno08x: buffer too small")
|
||||
errNoEvent = Error("bno08x: no sensor event available")
|
||||
errTimeout = Error("bno08x: operation timed out")
|
||||
errFrameTooLarge = Error("bno08x: frame exceeds maximum size")
|
||||
errNoBus = Error("bno08x: I2C bus not configured")
|
||||
errInvalidParam = Error("bno08x: invalid parameter")
|
||||
errHubError = Error("bno08x: sensor hub error")
|
||||
errIO = Error("bno08x: I/O error")
|
||||
)
|
||||
|
||||
// Metadata accessor methods (always available for any sensor type)
|
||||
|
||||
// ID returns the sensor ID.
|
||||
func (sv SensorValue) ID() SensorID {
|
||||
return sv.id
|
||||
}
|
||||
|
||||
// Status returns the sensor status flags.
|
||||
func (sv SensorValue) Status() uint8 {
|
||||
return sv.status
|
||||
}
|
||||
|
||||
// Sequence returns the sequence number.
|
||||
func (sv SensorValue) Sequence() uint8 {
|
||||
return sv.sequence
|
||||
}
|
||||
|
||||
// Delay returns the sensor delay value.
|
||||
func (sv SensorValue) Delay() uint8 {
|
||||
return sv.delay
|
||||
}
|
||||
|
||||
// Timestamp returns the sensor timestamp.
|
||||
func (sv SensorValue) Timestamp() uint64 {
|
||||
return sv.timestamp
|
||||
}
|
||||
|
||||
// Orientation data accessor methods
|
||||
|
||||
// Quaternion returns the quaternion value for rotation vector sensors.
|
||||
// Panics if called on a sensor type that doesn't provide quaternion data.
|
||||
func (sv SensorValue) Quaternion() Quaternion {
|
||||
switch sv.id {
|
||||
case SensorRotationVector, SensorGameRotationVector, SensorGeomagneticRotationVector,
|
||||
SensorARVRStabilizedRV, SensorARVRStabilizedGRV, SensorGyroIntegratedRV:
|
||||
return sv.quaternion
|
||||
default:
|
||||
panic("bno08x: Quaternion() called on non-rotation sensor type")
|
||||
}
|
||||
}
|
||||
|
||||
// QuaternionAccuracy returns the quaternion accuracy estimate.
|
||||
// Panics if called on a sensor type that doesn't provide quaternion accuracy.
|
||||
func (sv SensorValue) QuaternionAccuracy() float32 {
|
||||
switch sv.id {
|
||||
case SensorRotationVector, SensorGeomagneticRotationVector, SensorARVRStabilizedRV:
|
||||
return sv.quaternionAccuracy
|
||||
default:
|
||||
panic("bno08x: QuaternionAccuracy() called on sensor type without accuracy data")
|
||||
}
|
||||
}
|
||||
|
||||
// Linear measurement accessor methods
|
||||
|
||||
// Accelerometer returns the accelerometer vector.
|
||||
// Panics if called on a sensor type other than SensorAccelerometer.
|
||||
func (sv SensorValue) Accelerometer() Vector3 {
|
||||
if sv.id != SensorAccelerometer {
|
||||
panic("bno08x: Accelerometer() called on non-accelerometer sensor type")
|
||||
}
|
||||
return sv.accelerometer
|
||||
}
|
||||
|
||||
// LinearAcceleration returns the linear acceleration vector.
|
||||
// Panics if called on a sensor type other than SensorLinearAcceleration.
|
||||
func (sv SensorValue) LinearAcceleration() Vector3 {
|
||||
if sv.id != SensorLinearAcceleration {
|
||||
panic("bno08x: LinearAcceleration() called on wrong sensor type")
|
||||
}
|
||||
return sv.linearAcceleration
|
||||
}
|
||||
|
||||
// Gravity returns the gravity vector.
|
||||
// Panics if called on a sensor type other than SensorGravity.
|
||||
func (sv SensorValue) Gravity() Vector3 {
|
||||
if sv.id != SensorGravity {
|
||||
panic("bno08x: Gravity() called on non-gravity sensor type")
|
||||
}
|
||||
return sv.gravity
|
||||
}
|
||||
|
||||
// Gyroscope returns the gyroscope vector.
|
||||
// Panics if called on a sensor type other than SensorGyroscope.
|
||||
func (sv SensorValue) Gyroscope() Vector3 {
|
||||
if sv.id != SensorGyroscope {
|
||||
panic("bno08x: Gyroscope() called on non-gyroscope sensor type")
|
||||
}
|
||||
return sv.gyroscope
|
||||
}
|
||||
|
||||
// GyroscopeUncal returns the uncalibrated gyroscope data.
|
||||
// Panics if called on a sensor type other than SensorGyroscopeUncalibrated.
|
||||
func (sv SensorValue) GyroscopeUncal() GyroscopeUncalibrated {
|
||||
if sv.id != SensorGyroscopeUncalibrated {
|
||||
panic("bno08x: GyroscopeUncal() called on wrong sensor type")
|
||||
}
|
||||
return sv.gyroscopeUncal
|
||||
}
|
||||
|
||||
// MagneticField returns the magnetic field vector.
|
||||
// Panics if called on a sensor type other than SensorMagneticField.
|
||||
func (sv SensorValue) MagneticField() Vector3 {
|
||||
if sv.id != SensorMagneticField {
|
||||
panic("bno08x: MagneticField() called on wrong sensor type")
|
||||
}
|
||||
return sv.magneticField
|
||||
}
|
||||
|
||||
// MagneticFieldUncal returns the uncalibrated magnetic field data.
|
||||
// Panics if called on a sensor type other than SensorMagneticFieldUncalibrated.
|
||||
func (sv SensorValue) MagneticFieldUncal() MagneticFieldUncalibrated {
|
||||
if sv.id != SensorMagneticFieldUncalibrated {
|
||||
panic("bno08x: MagneticFieldUncal() called on wrong sensor type")
|
||||
}
|
||||
return sv.magneticFieldUncal
|
||||
}
|
||||
|
||||
// Raw sensor data accessor methods
|
||||
|
||||
// RawAccelerometer returns the raw accelerometer data.
|
||||
// Panics if called on a sensor type other than SensorRawAccelerometer.
|
||||
func (sv SensorValue) RawAccelerometer() RawVector3 {
|
||||
if sv.id != SensorRawAccelerometer {
|
||||
panic("bno08x: RawAccelerometer() called on wrong sensor type")
|
||||
}
|
||||
return sv.rawAccelerometer
|
||||
}
|
||||
|
||||
// RawGyroscope returns the raw gyroscope data.
|
||||
// Panics if called on a sensor type other than SensorRawGyroscope.
|
||||
func (sv SensorValue) RawGyroscope() RawGyroscope {
|
||||
if sv.id != SensorRawGyroscope {
|
||||
panic("bno08x: RawGyroscope() called on wrong sensor type")
|
||||
}
|
||||
return sv.rawGyroscope
|
||||
}
|
||||
|
||||
// RawMagnetometer returns the raw magnetometer data.
|
||||
// Panics if called on a sensor type other than SensorRawMagnetometer.
|
||||
func (sv SensorValue) RawMagnetometer() RawVector3 {
|
||||
if sv.id != SensorRawMagnetometer {
|
||||
panic("bno08x: RawMagnetometer() called on wrong sensor type")
|
||||
}
|
||||
return sv.rawMagnetometer
|
||||
}
|
||||
|
||||
// Environmental sensor accessor methods
|
||||
|
||||
// Pressure returns the pressure reading in hPa.
|
||||
// Panics if called on a sensor type other than SensorPressure.
|
||||
func (sv SensorValue) Pressure() float32 {
|
||||
if sv.id != SensorPressure {
|
||||
panic("bno08x: Pressure() called on non-pressure sensor type")
|
||||
}
|
||||
return sv.pressure
|
||||
}
|
||||
|
||||
// AmbientLight returns the ambient light reading in lux.
|
||||
// Panics if called on a sensor type other than SensorAmbientLight.
|
||||
func (sv SensorValue) AmbientLight() float32 {
|
||||
if sv.id != SensorAmbientLight {
|
||||
panic("bno08x: AmbientLight() called on wrong sensor type")
|
||||
}
|
||||
return sv.ambientLight
|
||||
}
|
||||
|
||||
// Humidity returns the humidity reading in percent.
|
||||
// Panics if called on a sensor type other than SensorHumidity.
|
||||
func (sv SensorValue) Humidity() float32 {
|
||||
if sv.id != SensorHumidity {
|
||||
panic("bno08x: Humidity() called on non-humidity sensor type")
|
||||
}
|
||||
return sv.humidity
|
||||
}
|
||||
|
||||
// Proximity returns the proximity reading in cm.
|
||||
// Panics if called on a sensor type other than SensorProximity.
|
||||
func (sv SensorValue) Proximity() float32 {
|
||||
if sv.id != SensorProximity {
|
||||
panic("bno08x: Proximity() called on non-proximity sensor type")
|
||||
}
|
||||
return sv.proximity
|
||||
}
|
||||
|
||||
// Temperature returns the temperature reading in °C.
|
||||
// Panics if called on a sensor type other than SensorTemperature.
|
||||
func (sv SensorValue) Temperature() float32 {
|
||||
if sv.id != SensorTemperature {
|
||||
panic("bno08x: Temperature() called on non-temperature sensor type")
|
||||
}
|
||||
return sv.temperature
|
||||
}
|
||||
|
||||
// Activity detection accessor methods
|
||||
|
||||
// TapDetector returns the tap detector data.
|
||||
// Panics if called on a sensor type other than SensorTapDetector.
|
||||
func (sv SensorValue) TapDetector() TapDetector {
|
||||
if sv.id != SensorTapDetector {
|
||||
panic("bno08x: TapDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.tapDetector
|
||||
}
|
||||
|
||||
// StepCounter returns the step counter value.
|
||||
// Panics if called on a sensor type other than SensorStepCounter.
|
||||
func (sv SensorValue) StepCounter() StepCounter {
|
||||
if sv.id != SensorStepCounter {
|
||||
panic("bno08x: StepCounter() called on wrong sensor type")
|
||||
}
|
||||
return sv.stepCounter
|
||||
}
|
||||
|
||||
// StepDetector returns the step detector data.
|
||||
// Panics if called on a sensor type other than SensorStepDetector.
|
||||
func (sv SensorValue) StepDetector() StepDetector {
|
||||
if sv.id != SensorStepDetector {
|
||||
panic("bno08x: StepDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.stepDetector
|
||||
}
|
||||
|
||||
// SignificantMotion returns the significant motion data.
|
||||
// Panics if called on a sensor type other than SensorSignificantMotion.
|
||||
func (sv SensorValue) SignificantMotion() SignificantMotion {
|
||||
if sv.id != SensorSignificantMotion {
|
||||
panic("bno08x: SignificantMotion() called on wrong sensor type")
|
||||
}
|
||||
return sv.significantMotion
|
||||
}
|
||||
|
||||
// ShakeDetector returns the shake detector data.
|
||||
// Panics if called on a sensor type other than SensorShakeDetector.
|
||||
func (sv SensorValue) ShakeDetector() ShakeDetector {
|
||||
if sv.id != SensorShakeDetector {
|
||||
panic("bno08x: ShakeDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.shakeDetector
|
||||
}
|
||||
|
||||
// FlipDetector returns the flip detector data.
|
||||
// Panics if called on a sensor type other than SensorFlipDetector.
|
||||
func (sv SensorValue) FlipDetector() uint16 {
|
||||
if sv.id != SensorFlipDetector {
|
||||
panic("bno08x: FlipDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.flipDetector
|
||||
}
|
||||
|
||||
// StabilityClassifier returns the stability classifier data.
|
||||
// Panics if called on a sensor type other than SensorStabilityClassifier.
|
||||
func (sv SensorValue) StabilityClassifier() StabilityClassifier {
|
||||
if sv.id != SensorStabilityClassifier {
|
||||
panic("bno08x: StabilityClassifier() called on wrong sensor type")
|
||||
}
|
||||
return sv.stabilityClassifier
|
||||
}
|
||||
|
||||
// StabilityDetector returns the stability detector value.
|
||||
// Panics if called on a sensor type other than SensorStabilityDetector.
|
||||
func (sv SensorValue) StabilityDetector() uint8 {
|
||||
if sv.id != SensorStabilityDetector {
|
||||
panic("bno08x: StabilityDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.stabilityDetector
|
||||
}
|
||||
|
||||
// ActivityClassifier returns the activity classification data.
|
||||
// Note: This field appears unused in decode.go, keeping for API compatibility.
|
||||
func (sv SensorValue) ActivityClassifier() ActivityClassification {
|
||||
return sv.activityClassifier
|
||||
}
|
||||
|
||||
// PersonalActivityClassifier returns the personal activity classifier data.
|
||||
// Panics if called on a sensor type other than SensorPersonalActivityClassifier.
|
||||
func (sv SensorValue) PersonalActivityClassifier() PersonalActivityClassifier {
|
||||
if sv.id != SensorPersonalActivityClassifier {
|
||||
panic("bno08x: PersonalActivityClassifier() called on wrong sensor type")
|
||||
}
|
||||
return sv.personalActivityClassifier
|
||||
}
|
||||
|
||||
// SleepDetector returns the sleep detector value.
|
||||
// Panics if called on a sensor type other than SensorSleepDetector.
|
||||
func (sv SensorValue) SleepDetector() uint8 {
|
||||
if sv.id != SensorSleepDetector {
|
||||
panic("bno08x: SleepDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.sleepDetector
|
||||
}
|
||||
|
||||
// TiltDetector returns the tilt detector value.
|
||||
// Panics if called on a sensor type other than SensorTiltDetector.
|
||||
func (sv SensorValue) TiltDetector() uint8 {
|
||||
if sv.id != SensorTiltDetector {
|
||||
panic("bno08x: TiltDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.tiltDetector
|
||||
}
|
||||
|
||||
// PocketDetector returns the pocket detector value.
|
||||
// Panics if called on a sensor type other than SensorPocketDetector.
|
||||
func (sv SensorValue) PocketDetector() uint8 {
|
||||
if sv.id != SensorPocketDetector {
|
||||
panic("bno08x: PocketDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.pocketDetector
|
||||
}
|
||||
|
||||
// CircleDetector returns the circle detector value.
|
||||
// Panics if called on a sensor type other than SensorCircleDetector.
|
||||
func (sv SensorValue) CircleDetector() uint8 {
|
||||
if sv.id != SensorCircleDetector {
|
||||
panic("bno08x: CircleDetector() called on wrong sensor type")
|
||||
}
|
||||
return sv.circleDetector
|
||||
}
|
||||
|
||||
// HeartRateMonitor returns the heart rate monitor value.
|
||||
// Panics if called on a sensor type other than SensorHeartRateMonitor.
|
||||
func (sv SensorValue) HeartRateMonitor() uint16 {
|
||||
if sv.id != SensorHeartRateMonitor {
|
||||
panic("bno08x: HeartRateMonitor() called on wrong sensor type")
|
||||
}
|
||||
return sv.heartRateMonitor
|
||||
}
|
||||
+13
-8
@@ -1,24 +1,23 @@
|
||||
// Package buzzer provides a very simplistic driver for a connected buzzer or low-fidelity speaker.
|
||||
//
|
||||
package buzzer // import "tinygo.org/x/drivers/buzzer"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
)
|
||||
|
||||
// Device wraps a GPIO connection to a buzzer.
|
||||
type Device struct {
|
||||
pin machine.Pin
|
||||
pin pin.OutputFunc
|
||||
High bool
|
||||
BPM float64
|
||||
}
|
||||
|
||||
// New returns a new buzzer driver given which pin to use
|
||||
func New(pin machine.Pin) Device {
|
||||
func New(pin pin.Output) Device {
|
||||
return Device{
|
||||
pin: pin,
|
||||
pin: pin.Set,
|
||||
High: false,
|
||||
BPM: 96.0,
|
||||
}
|
||||
@@ -26,14 +25,14 @@ func New(pin machine.Pin) Device {
|
||||
|
||||
// On sets the buzzer to a high state.
|
||||
func (l *Device) On() (err error) {
|
||||
l.pin.Set(true)
|
||||
l.pin.High()
|
||||
l.High = true
|
||||
return
|
||||
}
|
||||
|
||||
// Off sets the buzzer to a low state.
|
||||
func (l *Device) Off() (err error) {
|
||||
l.pin.Set(false)
|
||||
l.pin.Low()
|
||||
l.High = false
|
||||
return
|
||||
}
|
||||
@@ -55,6 +54,12 @@ func (l *Device) Tone(hz, duration float64) (err error) {
|
||||
|
||||
tempo := ((60 / l.BPM) * (duration * 1000))
|
||||
|
||||
// no tone during rest, just let the duration pass.
|
||||
if hz == Rest {
|
||||
time.Sleep(time.Duration(tempo) * time.Millisecond)
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0.0; i < tempo*1000; i += tone * 2.0 {
|
||||
if err = l.On(); err != nil {
|
||||
return
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
package buzzer
|
||||
|
||||
const (
|
||||
Whole = 4
|
||||
Half = 2
|
||||
Quarter = 1
|
||||
Whole = 4.0
|
||||
Half = 2.0
|
||||
Quarter = 1.0
|
||||
Eighth = 0.500
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// See ../../image/README.md for the usage.
|
||||
|
||||
func main() {
|
||||
err := run(os.Args)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(args []string) error {
|
||||
if len(args) < 2 {
|
||||
return fmt.Errorf("usage: %s FILE")
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("const %s = \"\" +\n", strings.Replace(args[1], ".", "_", -1))
|
||||
|
||||
i := 0
|
||||
max := 32
|
||||
for i = 0; i < len(b); i++ {
|
||||
bb := b[i]
|
||||
if (i % max) == 0 {
|
||||
fmt.Printf(" \"")
|
||||
}
|
||||
fmt.Printf("\\x%02X", bb)
|
||||
if (i%max) == max-1 && i != len(b)-1 {
|
||||
fmt.Printf("\" + \n")
|
||||
}
|
||||
}
|
||||
if (i % max) < max-1 {
|
||||
fmt.Printf("\"\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,711 @@
|
||||
// Package comboat implements WiFi driver for the Aithinker-Combo-AT WiFi
|
||||
// device found on the Elecrow W5 rp2040 and rp2350 devices. Ths WiFi device
|
||||
// is a RTL8720d variant. The driver interface is via AT command set over UART
|
||||
// (see reference docs below).
|
||||
//
|
||||
// NOTE: the driver doesn't support UDP/TCP server connections in STA mode,
|
||||
// currently. UDP/TCP/TLS client connections are supported in STA mode.
|
||||
//
|
||||
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/instruction/index.html
|
||||
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/command-set/index.html
|
||||
// https://aithinker-combo-guide.readthedocs.io/en/latest/docs/command-examples/index.html
|
||||
|
||||
package comboat // import "tinygo.org/x/drivers/comboat"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"machine"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
"tinygo.org/x/drivers/netlink"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
BaudRate uint32
|
||||
Uart *machine.UART
|
||||
Tx machine.Pin
|
||||
Rx machine.Pin
|
||||
}
|
||||
|
||||
type socket struct {
|
||||
protocol int
|
||||
id string
|
||||
rx chan []byte
|
||||
remainder []byte
|
||||
laddr netip.AddrPort // Set in Bind()
|
||||
}
|
||||
|
||||
type device struct {
|
||||
cfg *Config
|
||||
uart *machine.UART
|
||||
uartMu sync.Mutex
|
||||
mac net.HardwareAddr
|
||||
ip netip.Addr
|
||||
gateway netip.Addr
|
||||
buf [1500]byte
|
||||
pos int
|
||||
last []byte
|
||||
ok chan bool
|
||||
txReady chan bool
|
||||
accept chan string
|
||||
err chan error
|
||||
sockets [8]*socket
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewDevice(cfg *Config) *device {
|
||||
return &device{
|
||||
cfg: cfg,
|
||||
ok: make(chan bool),
|
||||
txReady: make(chan bool),
|
||||
accept: make(chan string),
|
||||
err: make(chan error),
|
||||
}
|
||||
}
|
||||
|
||||
func logDebug(msg string) {
|
||||
//println("[DEBUG] " + msg)
|
||||
}
|
||||
|
||||
func logError(msg string) {
|
||||
println("[ERROR] " + msg)
|
||||
}
|
||||
|
||||
func split(resp []byte, part int, del, on string) string {
|
||||
parts := bytes.Split(resp, []byte(del))
|
||||
if part >= len(parts) {
|
||||
return "Split parts error getting " + on
|
||||
}
|
||||
return string(parts[part])
|
||||
}
|
||||
|
||||
func (d *device) getFWVersion() string {
|
||||
return split(d.last, 1, ":", "FW version")
|
||||
}
|
||||
|
||||
func (d *device) saveMAC() {
|
||||
raw := split(d.last, 1, ":", "MAC")
|
||||
if len(raw) > 11 {
|
||||
macStr := fmt.Sprintf("%s:%s:%s:%s:%s:%s",
|
||||
raw[0:2], raw[2:4], raw[4:6],
|
||||
raw[6:8], raw[8:10], raw[10:12])
|
||||
d.mac, _ = net.ParseMAC(macStr)
|
||||
}
|
||||
}
|
||||
|
||||
var countryCodes = map[int]string{
|
||||
1: "JP Japan",
|
||||
2: "American Samoa",
|
||||
3: "CA Canada",
|
||||
4: "US",
|
||||
5: "CN China",
|
||||
6: "Hong Kong, China",
|
||||
7: "Taiwan, China",
|
||||
8: "MO Macau, China",
|
||||
9: "IL Israel",
|
||||
10: "Singapore",
|
||||
11: "KR South Korea",
|
||||
12: "TR Türkiye",
|
||||
13: "AU Australia",
|
||||
14: "ZA South Africa",
|
||||
15: "BR Brazil",
|
||||
}
|
||||
|
||||
func (d *device) getCountry() (code string) {
|
||||
code = split(d.last, 1, ":", "county code")
|
||||
codeNum, err := strconv.Atoi(code)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if val, ok := countryCodes[codeNum]; ok {
|
||||
code = val
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *device) saveIP() {
|
||||
ipStr := split(d.last, 7, ",", "IP address")
|
||||
gwStr := split(d.last, 8, ",", "gateway address")
|
||||
d.ip, _ = netip.ParseAddr(ipStr)
|
||||
d.gateway, _ = netip.ParseAddr(gwStr)
|
||||
}
|
||||
|
||||
func (d *device) execute(cmd string, timeout int) (err error) {
|
||||
logDebug("EXECUTE " + cmd)
|
||||
|
||||
d.uartMu.Lock()
|
||||
_, err = d.uart.Write([]byte(cmd + "\r\n"))
|
||||
d.uartMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t := time.NewTicker(time.Duration(timeout) * time.Millisecond)
|
||||
defer t.Stop()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
return errors.New("Timed out")
|
||||
case <-d.ok:
|
||||
return
|
||||
case err = <-d.err:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) send(cmd string, timeout int) (err error) {
|
||||
logDebug("EXECUTE " + cmd)
|
||||
|
||||
d.uartMu.Lock()
|
||||
_, err = d.uart.Write([]byte(cmd + "\r\n"))
|
||||
d.uartMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t := time.NewTicker(time.Duration(timeout) * time.Millisecond)
|
||||
defer t.Stop()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
return errors.New("Timed out")
|
||||
case <-d.txReady:
|
||||
return
|
||||
case err = <-d.err:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) findSocket(id string) (*socket, error) {
|
||||
for _, s := range d.sockets {
|
||||
if s.id == id {
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("Socket not found with id: " + id)
|
||||
}
|
||||
|
||||
func (d *device) getSocket(sockfd int) (*socket, error) {
|
||||
if sockfd < 0 || sockfd+1 > len(d.sockets) {
|
||||
return nil, netdev.ErrInvalidSocketFd
|
||||
}
|
||||
if d.sockets[sockfd] == nil {
|
||||
return nil, netdev.ErrInvalidSocketFd
|
||||
}
|
||||
return d.sockets[sockfd], nil
|
||||
}
|
||||
|
||||
func (d *device) handle(event []byte) {
|
||||
logDebug("GOT EVENT " + string(event))
|
||||
switch {
|
||||
|
||||
// SocketDisconnect,<id>
|
||||
case bytes.HasPrefix(event, []byte("SocketDisconnect")):
|
||||
id := split(event, 1, ",", "SocketDisconnect")
|
||||
s, err := d.findSocket(id)
|
||||
if err == nil {
|
||||
close(s.rx) // Sends io.EOF
|
||||
}
|
||||
|
||||
// SocketSeed,<id>,<server id>
|
||||
case bytes.HasPrefix(event, []byte("SocketSeed,2,1")):
|
||||
//d.uart.Write([]byte("AT+SOCKET?" + "\r\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) processUART() {
|
||||
|
||||
if d.pos == 1 && d.buf[0] == '>' {
|
||||
d.pos = 0
|
||||
logDebug("GOT >")
|
||||
d.txReady <- true
|
||||
}
|
||||
|
||||
sofar := d.buf[:d.pos]
|
||||
|
||||
if !bytes.HasSuffix(sofar, []byte("\r\n")) {
|
||||
return
|
||||
}
|
||||
|
||||
// Strip CR/LF off end
|
||||
sofar = sofar[:len(sofar)-2]
|
||||
|
||||
switch {
|
||||
|
||||
case bytes.HasPrefix(sofar, []byte("+EVENT:SocketDown")):
|
||||
// +EVENT:SocketDown,<id>,<length>,<data>
|
||||
parts := bytes.SplitN(sofar, []byte(","), 4)
|
||||
if len(parts) != 4 {
|
||||
logError("Error parsing +EVENT:SocketDown: " + string(sofar))
|
||||
d.pos = 0
|
||||
return
|
||||
}
|
||||
id := string(parts[1])
|
||||
length, err := strconv.Atoi(string(parts[2]))
|
||||
if err != nil {
|
||||
logError("Error parsing length from: " + string(parts[2]))
|
||||
d.pos = 0
|
||||
return
|
||||
}
|
||||
if length != len(parts[3]) {
|
||||
// This can happen if <data> actually contains a CR/LF.
|
||||
// Return without resetting d.pos to continue reading
|
||||
// in the full <data>.
|
||||
return
|
||||
}
|
||||
s, err := d.findSocket(id)
|
||||
if err != nil {
|
||||
logError(err.Error())
|
||||
d.pos = 0
|
||||
return
|
||||
}
|
||||
logDebug("GOT +EVENT:SocketDown," + id + "," + string(parts[2]))
|
||||
d.pos = 0
|
||||
data := make([]byte, len(parts[3]))
|
||||
copy(data, parts[3])
|
||||
s.rx <- data
|
||||
|
||||
case bytes.HasPrefix(sofar, []byte("OK")):
|
||||
d.pos = 0
|
||||
logDebug("GOT OK")
|
||||
d.ok <- true
|
||||
|
||||
case bytes.HasPrefix(sofar, []byte("ERROR")):
|
||||
d.pos = 0
|
||||
logDebug("GOT ERROR")
|
||||
errStr := getErrStr(d.last)
|
||||
d.err <- errors.New(errStr)
|
||||
|
||||
case bytes.HasPrefix(sofar, []byte("+EVENT:")):
|
||||
d.pos = 0
|
||||
event := sofar[len("+EVENT:"):]
|
||||
d.handle(event)
|
||||
|
||||
default:
|
||||
// Catch everything else and store in d.last
|
||||
d.pos = 0
|
||||
size := len(sofar)
|
||||
if size > 0 {
|
||||
d.last = make([]byte, size)
|
||||
copy(d.last, sofar[:size])
|
||||
logDebug("GOT LINE " + string(d.last))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) serviceUART() {
|
||||
for {
|
||||
d.uartMu.Lock()
|
||||
for d.uart.Buffered() > 0 {
|
||||
if d.pos >= len(d.buf) {
|
||||
println("Trying to write past buffer")
|
||||
d.pos = 0
|
||||
break
|
||||
}
|
||||
var err error
|
||||
d.buf[d.pos], err = d.uart.ReadByte()
|
||||
if err == nil {
|
||||
d.pos++
|
||||
d.processUART()
|
||||
}
|
||||
}
|
||||
d.uartMu.Unlock()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) NetConnect(params *netlink.ConnectParams) error {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
d.uart = d.cfg.Uart
|
||||
d.uart.Configure(machine.UARTConfig{
|
||||
BaudRate: d.cfg.BaudRate,
|
||||
TX: d.cfg.Tx,
|
||||
RX: d.cfg.Rx,
|
||||
})
|
||||
|
||||
go d.serviceUART()
|
||||
|
||||
fmt.Printf("\r\n")
|
||||
fmt.Printf("TinyGo Combo-AT WiFi network device driver\r\n")
|
||||
|
||||
fmt.Printf("\r\n")
|
||||
fmt.Printf("Driver version : %s\r\n", drivers.Version)
|
||||
|
||||
if len(params.Ssid) == 0 {
|
||||
return netlink.ErrMissingSSID
|
||||
}
|
||||
|
||||
// AT Test to see if device is alive
|
||||
if err := d.execute("AT", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Disable echo
|
||||
if err := d.execute("ATE0", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get FW version
|
||||
if err := d.execute("AT+GMR", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Combo-AT firmware version : %s\r\n", d.getFWVersion())
|
||||
|
||||
// Get/save MAC addresses
|
||||
if err := d.execute("AT+CIPSTAMAC_DEF?", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
d.saveMAC()
|
||||
fmt.Printf("MAC address : %s\r\n", d.mac.String())
|
||||
|
||||
// Set country code US
|
||||
if err := d.execute("AT+WCOUNTRY=4", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get country code
|
||||
if err := d.execute("AT+WCOUNTRY?", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("WiFi country code : %s\r\n", d.getCountry())
|
||||
|
||||
// Set Wi-Fi working mode to STA and save to flash
|
||||
if err := d.execute("AT+WMODE=1,1", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Connect to Wifi AP (keep trying until connected)
|
||||
fmt.Printf("\r\n")
|
||||
cmd := "AT+WJAP=" + params.Ssid + "," + params.Passphrase
|
||||
|
||||
for {
|
||||
fmt.Printf("Connecting to WiFi SSID '%s'...", params.Ssid)
|
||||
if err := d.execute(cmd, 20000); err != nil {
|
||||
fmt.Printf("FAILED (%s)\r\n", err.Error())
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("CONNECTED\r\n")
|
||||
|
||||
// Automatically reconnect to Wi-Fi after power on
|
||||
if err := d.execute("AT+WAUTOCONN=1", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get/save IP/gateway addresses
|
||||
if err := d.execute("AT+WJAP?", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
d.saveIP()
|
||||
|
||||
fmt.Printf("\r\n")
|
||||
fmt.Printf("DHCP-assigned IP : %s\r\n", d.ip)
|
||||
fmt.Printf("DHCP-assigned gateway : %s\r\n", d.gateway)
|
||||
fmt.Printf("\r\n")
|
||||
|
||||
// Set socket receiving mode to active
|
||||
if err := d.execute("AT+SOCKETRECVCFG=1", 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *device) NetDisconnect() {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
// Disconnect from WiFi AP
|
||||
d.execute("AT+WDISCONNECT", 1000)
|
||||
}
|
||||
|
||||
func (d *device) NetNotify(cb func(netlink.Event)) {
|
||||
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
|
||||
}
|
||||
|
||||
func (d *device) GetHardwareAddr() (net.HardwareAddr, error) {
|
||||
return d.mac, nil
|
||||
}
|
||||
|
||||
func (d *device) _getHostByName(name string) (ip netip.Addr, err error) {
|
||||
if err = d.execute("AT+WDOMAIN="+name, 1000); err != nil {
|
||||
return
|
||||
}
|
||||
ipStr := split(d.last, 1, ":", "host by name")
|
||||
return netip.ParseAddr(ipStr)
|
||||
}
|
||||
|
||||
func (d *device) GetHostByName(name string) (ip netip.Addr, err error) {
|
||||
|
||||
// If it's already a dotted-network address, and not a host name,
|
||||
// return it
|
||||
ip, err = netip.ParseAddr(name)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
return d._getHostByName(name)
|
||||
}
|
||||
|
||||
func (d *device) Addr() (netip.Addr, error) {
|
||||
return d.ip, nil
|
||||
}
|
||||
|
||||
func (d *device) Socket(domain, stype, protocol int) (int, error) {
|
||||
|
||||
switch domain {
|
||||
case netdev.AF_INET:
|
||||
default:
|
||||
return -1, netdev.ErrFamilyNotSupported
|
||||
}
|
||||
|
||||
switch {
|
||||
case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
|
||||
case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
|
||||
case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
|
||||
default:
|
||||
return -1, netdev.ErrProtocolNotSupported
|
||||
}
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
// Search for empty slot in sockets array
|
||||
for fd, s := range d.sockets {
|
||||
if s == nil {
|
||||
// Found one
|
||||
d.sockets[fd] = &socket{
|
||||
protocol: protocol,
|
||||
rx: make(chan []byte, 10),
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
}
|
||||
|
||||
return -1, netdev.ErrNoMoreSockets
|
||||
}
|
||||
|
||||
func (d *device) Bind(sockfd int, ip netip.AddrPort) error {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.laddr = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *device) Connect(sockfd int, host string, ip netip.AddrPort) error {
|
||||
|
||||
var addr string
|
||||
var cmd string
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if host == "" {
|
||||
addr = ip.Addr().String()
|
||||
} else {
|
||||
ip, err := d._getHostByName(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr = ip.String()
|
||||
}
|
||||
port := strconv.Itoa(int(ip.Port()))
|
||||
|
||||
switch s.protocol {
|
||||
case netdev.IPPROTO_UDP:
|
||||
cmd = "AT+SOCKET=2," + addr + "," + port
|
||||
case netdev.IPPROTO_TCP:
|
||||
cmd = "AT+SOCKET=4," + addr + "," + port
|
||||
case netdev.IPPROTO_TLS:
|
||||
cmd = "AT+SOCKET=7," + addr + "," + port
|
||||
}
|
||||
|
||||
if cmd == "" {
|
||||
return netdev.ErrProtocolNotSupported
|
||||
}
|
||||
|
||||
if err := d.execute(cmd, 20000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.id = split(d.last, 1, "=", "connection ID")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *device) Listen(sockfd, backlog int) error {
|
||||
|
||||
// TODO Creating a TCP server socket isn't working when in STA mode,
|
||||
// TODO returning error "Socket bind error".
|
||||
// TODO The reference example shows a TCP server example in AP mode.
|
||||
|
||||
/*
|
||||
var cmd string
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
port := strconv.Itoa(int(s.laddr.Port()))
|
||||
|
||||
switch s.protocol {
|
||||
case netdev.IPPROTO_UDP:
|
||||
cmd = "AT+SOCKET=1," + port
|
||||
case netdev.IPPROTO_TCP:
|
||||
cmd = "AT+SOCKET=3," + port
|
||||
}
|
||||
|
||||
if cmd == "" {
|
||||
return netdev.ErrProtocolNotSupported
|
||||
}
|
||||
|
||||
if err := d.execute(cmd, 20000); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.id = split(d.last, 1, "=", "connection ID")
|
||||
*/
|
||||
|
||||
return netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
func (d *device) Accept(sockfd int) (int, netip.AddrPort, error) {
|
||||
return 0, netip.AddrPort{}, netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
func (d *device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("AT+SOCKETSEND=%s,%d", s.id, len(buf))
|
||||
|
||||
if err := d.send(cmd, 1000); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// AT+SOCKETSEND will sub-packet send data into 1024-byte chunks,
|
||||
// automatically, so send the full buffer in one shot, even if it's
|
||||
// bigger than 1024 bytes.
|
||||
|
||||
d.uartMu.Lock()
|
||||
n, err := d.uart.Write(buf)
|
||||
d.uartMu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Expecting "OK" after good send, or "ERROR"
|
||||
|
||||
t := time.NewTicker(time.Duration(1000) * time.Millisecond)
|
||||
defer t.Stop()
|
||||
|
||||
select {
|
||||
case <-t.C:
|
||||
return 0, errors.New("Timed out")
|
||||
case <-d.ok:
|
||||
return n, nil
|
||||
case err = <-d.err:
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
func (d *device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 1. Use leftover data first
|
||||
if len(s.remainder) > 0 {
|
||||
n := copy(buf, s.remainder)
|
||||
s.remainder = s.remainder[n:]
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// 2. Get new data from the channel
|
||||
data, ok := <-s.rx
|
||||
if !ok {
|
||||
// Socket closed, return EOF
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// 3. Copy data, handle leftovers
|
||||
n := copy(buf, data)
|
||||
if n < len(data) {
|
||||
s.remainder = data[n:]
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *device) Close(sockfd int) error {
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
s, err := d.getSocket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete socket only if connection was successful (s.id is set)
|
||||
if s.id != "" {
|
||||
cmd := fmt.Sprintf("AT+SOCKETDEL=%s", s.id)
|
||||
if err = d.execute(cmd, 1000); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d.sockets[sockfd] = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *device) SetSockOpt(sockfd, level, opt int, value interface{}) error {
|
||||
return netdev.ErrNotSupported
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package comboat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var errStrings = map[int]string{
|
||||
|
||||
// System framework related error codes
|
||||
|
||||
0: "success",
|
||||
1: "The command is not supported (the combo framework contains the command but the current platform has not transplanted or adapted to support it)",
|
||||
2: "The command parameters contain unsupported operations (the current platform only supports some operations for this command)",
|
||||
3: "The instruction format is incorrect (this refers to the wrong number of parameters, for example, two parameters are required, but only one parameter is entered)",
|
||||
4: "Parameter error (the content of the parameter is wrong, for example, a number between 0 and 9 is required, but 10 or xyz is passed in, which is a parameter error)",
|
||||
5: "Parameter length error (command length exceeds the maximum supported length)",
|
||||
31: "The current command has not ended and needs to report the status asynchronously. This value is used by the state machine to determine the use of the command and no message is returned.",
|
||||
32: "Unknown error (or unhandled error type)",
|
||||
|
||||
// Common error codes
|
||||
|
||||
33: "malloc error",
|
||||
34: "Failed to read buf",
|
||||
35: "Failed to write buf",
|
||||
36: "Configuration error (configuration error loaded from memory, for example, we set port -1 for OTA upgrade, and check port error when executing AT+OTA, then configuration error will be reported)",
|
||||
37: "Failed to create task",
|
||||
38: "Flash read and write failure",
|
||||
39: "Serial port configuration error, unsupported baud rate",
|
||||
40: "Serial port configuration error, unsupported data bits",
|
||||
41: "Serial port configuration error, unsupported stop bit",
|
||||
42: "Serial port configuration error, unsupported parity bit",
|
||||
43: "Serial port configuration error, unsupported flow control",
|
||||
44: "Serial port configuration failed",
|
||||
45: "Wrong username/password",
|
||||
46: "Low power mode error or unsupported low power mode",
|
||||
47: "Uninitialized configuration data error (including io mapping data)",
|
||||
63: "General error code (without other information)",
|
||||
|
||||
// Wi-Fi related error codes
|
||||
|
||||
64: "Wi-Fi not initialized or initialization failed",
|
||||
65: "Wi-Fi mode error (unable to connect to Wi-Fi in single AP mode)",
|
||||
66: "Wi-Fi connection failed",
|
||||
67: "Wi-Fi connection successful, error in obtaining IP (DHCP)",
|
||||
68: "Failed to obtain encryption method",
|
||||
69: "The specified AP was not found.",
|
||||
70: "Wi-Fi scan start failed",
|
||||
71: "Wi-Fi scan timeout",
|
||||
72: "Failed to enable AP hotspot",
|
||||
73: "Failed to obtain the Wi-Fi information of the router or the AP information that you enabled yourself",
|
||||
74: "The network card (STA/AP) is not running",
|
||||
75: "Wi-Fi country code error (unsupported Wi-Fi country code)",
|
||||
76: "The current network configuration mode is wrong.",
|
||||
95: "Wi-Fi connection unknown error",
|
||||
|
||||
// Socket related error codes
|
||||
|
||||
96: "Failed to create socket",
|
||||
97: "Socket connection failed",
|
||||
98: "DNS Failure",
|
||||
99: "The socket status is wrong (for example, TCP is not connected yet)",
|
||||
100: "Socket type error",
|
||||
101: "Socket send failed",
|
||||
102: "Socket receive failed",
|
||||
103: "Socket monitoring thread creation failed",
|
||||
104: "Socket bind error",
|
||||
105: "The current connection cannot be transparently linked (wrong socket type or number)",
|
||||
106: "PING test failed (all packets lost)",
|
||||
107: "Wi-Fi country code error (unsupported Wi-Fi country code)",
|
||||
108: "SSL Config Error",
|
||||
109: "SSL verification error (usually caused by unsupported SSL encryption type or certificate error)",
|
||||
127: "Unknown socket error",
|
||||
}
|
||||
|
||||
func getErrStr(errLine []byte) (errStr string) {
|
||||
errStr = "Can't parse ERROR response"
|
||||
tokens := bytes.Split(errLine, []byte(":"))
|
||||
if len(tokens) > 1 {
|
||||
errCode, err := strconv.Atoi(string(tokens[1]))
|
||||
if err == nil {
|
||||
errStr = errStrings[errCode]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// Loop the given times, where one loop takes four CPU cycles.
|
||||
bool tinygo_drivers_sleep(uint32_t cycles) {
|
||||
// In this function, a [n] comment indicates the number of cycles an
|
||||
// instruction or a set of instructions take. This is typically 1 for most
|
||||
// arithmetic instructions, and a bit more for branches.
|
||||
#if __ARM_ARCH_6M__ || __ARM_ARCH_7M__ || __ARM_ARCH_7EM__
|
||||
// Inline assembly for Cortex-M0/M0+/M3/M4/M7.
|
||||
// The Cortex-M0 (but not M0+) takes one more cycle, so is off by 12.5%.
|
||||
// Others should be basically cycle-accurate (with a slight overhead to
|
||||
// calculate the number of cycles). Unfortunately, there doesn't appear to
|
||||
// be a preprocessor macro to detect the Cortex-M0 specifically (although we
|
||||
// could rely on macros like NRF51).
|
||||
|
||||
// Each loop takes 8 cycles (5 nops, 1 sub, and 2 for the branch).
|
||||
uint32_t loops = (cycles + 7) / 8;
|
||||
__asm__ __volatile__(
|
||||
"1:\n\t"
|
||||
"nop\n\t" // [5] nops
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"subs %[loops], #1\n\t" // [1]
|
||||
"bne 1b" // [1-4], at least 2 cycles if taken
|
||||
: [loops]"+r"(loops)
|
||||
);
|
||||
return true;
|
||||
#elif __XTENSA__
|
||||
// Inline assembly for Xtensa.
|
||||
// I don't know exactly how many cycles a branch takes, so I've taken a
|
||||
// conservative guess and assume it takes only one cycle. In practice, it's
|
||||
// probably more than that.
|
||||
uint32_t loops = (cycles + 7) / 8;
|
||||
__asm__ __volatile__(
|
||||
"1:\n\t"
|
||||
"nop\n\t" // [6] nops
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"nop\n\t"
|
||||
"addi %[loops], %[loops], -1\n\t" // [1]
|
||||
"bnez %[loops], 1b" // [1?]
|
||||
: [loops]"+r"(loops)
|
||||
);
|
||||
return true;
|
||||
#else
|
||||
// Unknown architecture, so fall back to time.Sleep.
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package delay
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
bool tinygo_drivers_sleep(uint32_t ticks);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Sleep for a very precise short duration by busy-waiting for the given time.
|
||||
// This is not an efficient way to sleep: it will needlessly burn cycles while
|
||||
// sleeping. But it is useful for sleeping for a very short duration, for
|
||||
// example for bit-banged protocols.
|
||||
//
|
||||
// Longer durations (longer than a few milliseconds) will be handled by calling
|
||||
// time.Sleep instead.
|
||||
//
|
||||
// This function should be called with a constant duration value, in which case
|
||||
// the call will typically be fully inlined and only take up around nine
|
||||
// instructions for the entire loop.
|
||||
//
|
||||
//go:inline
|
||||
func Sleep(duration time.Duration) {
|
||||
if time.Duration(uint32(duration)&0xff_ffff) != duration {
|
||||
// This is a long duration (more than 16ms) which shouldn't be done by
|
||||
// busy-waiting.
|
||||
time.Sleep(duration)
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate the number of cycles we should sleep:
|
||||
// cycles = duration * freq / 1e9
|
||||
// Avoiding a 64-bit division:
|
||||
// cycles = duration * (freq/1000_000) / 1000
|
||||
//
|
||||
// This assumes:
|
||||
// * The CPU frequency is a constant and can trivially be
|
||||
// const-propagated, therefore the divide by 1000_000 is done at compile
|
||||
// time.
|
||||
// * The CPU frequency is a multiple of 1000_000, which is true for most
|
||||
// chips (examples: 16MHz, 48MHz, 120MHz, etc).
|
||||
// * The division by 1000 can be done efficiently (Cortex-M3 and up), or
|
||||
// can be fully const-propagated.
|
||||
// * The CPU frequency is lower than 256MHz. If it is higher, long sleep
|
||||
// times (1-16ms) may not work correctly.
|
||||
cycles := uint32(duration) * (machine.CPUFrequency() / 1000_000) / 1000
|
||||
slept := C.tinygo_drivers_sleep(C.uint32_t(cycles))
|
||||
if !slept {
|
||||
// Fallback for platforms without inline assembly support.
|
||||
time.Sleep(duration)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//go:build tinygo
|
||||
|
||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||
//
|
||||
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
|
||||
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
|
||||
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Celsius and Fahrenheit temperature scales
|
||||
type TemperatureScale uint8
|
||||
|
||||
func (t TemperatureScale) convertToFloat(temp int16) float32 {
|
||||
if t == C {
|
||||
return float32(temp) / 10
|
||||
} else {
|
||||
// Fahrenheit
|
||||
return float32(temp)*(9.0/50.) + 32.
|
||||
}
|
||||
}
|
||||
|
||||
// All functions return ErrorCode instance as error. This class can be used for more efficient error processing
|
||||
type ErrorCode uint8
|
||||
|
||||
const (
|
||||
startTimeout = time.Millisecond * 200
|
||||
startingLow = time.Millisecond * 20
|
||||
|
||||
C TemperatureScale = iota
|
||||
F
|
||||
|
||||
ChecksumError ErrorCode = iota
|
||||
NoSignalError
|
||||
NoDataError
|
||||
UpdateError
|
||||
UninitializedDataError
|
||||
)
|
||||
|
||||
// error interface implementation for ErrorCode
|
||||
func (e ErrorCode) Error() string {
|
||||
switch e {
|
||||
case ChecksumError:
|
||||
// DHT returns ChecksumError if all the data from the sensor was received, but the checksum does not match.
|
||||
return "checksum mismatch"
|
||||
case NoSignalError:
|
||||
// DHT returns NoSignalError if there was no reply from the sensor. Check sensor connection or the correct pin
|
||||
// sis chosen,
|
||||
return "no signal"
|
||||
case NoDataError:
|
||||
// DHT returns NoDataError if the connection was successfully initialized, but not all 40 bits from
|
||||
// the sensor is received
|
||||
return "no data"
|
||||
case UpdateError:
|
||||
// DHT returns UpdateError if ReadMeasurements function is called before time specified in UpdatePolicy or
|
||||
// less than 2 seconds after past measurement
|
||||
return "cannot update now"
|
||||
case UninitializedDataError:
|
||||
// DHT returns UninitializedDataError if user attempts to access data before first measurement
|
||||
return "no measurements done"
|
||||
}
|
||||
// should never be reached
|
||||
return "unknown error"
|
||||
}
|
||||
|
||||
// Update policy of the DHT device. UpdateTime cannot be shorter than 2 seconds. According to dht specification sensor
|
||||
// will return undefined data if update requested less than 2 seconds before last usage
|
||||
type UpdatePolicy struct {
|
||||
UpdateTime time.Duration
|
||||
UpdateAutomatically bool
|
||||
}
|
||||
|
||||
var (
|
||||
// timeout counter equal to number of ticks per 1 millisecond
|
||||
timeout counter
|
||||
)
|
||||
|
||||
func init() {
|
||||
timeout = cyclesPerMillisecond()
|
||||
}
|
||||
|
||||
func cyclesPerMillisecond() counter {
|
||||
freq := machine.CPUFrequency()
|
||||
freq /= 1000
|
||||
return counter(freq)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package dht
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// DeviceType is the enum type for device type
|
||||
type DeviceType uint8
|
||||
|
||||
const (
|
||||
DHT11 DeviceType = iota
|
||||
DHT22
|
||||
)
|
||||
|
||||
// extractData parses information received from the sensor.
|
||||
// The 2 first buffers are for the humidity and
|
||||
// the 2 following corresponds to the temperature.
|
||||
func (d DeviceType) extractData(buf []byte) (temp int16, hum uint16) {
|
||||
switch d {
|
||||
case DHT11:
|
||||
hum = 10*uint16(buf[0]) + uint16(buf[1])
|
||||
temp = int16(buf[2])
|
||||
if buf[3]&0x80 > 0 {
|
||||
temp = -1 - temp
|
||||
}
|
||||
temp *= 10
|
||||
temp += int16(buf[3] & 0x0f)
|
||||
case DHT22:
|
||||
hum = binary.BigEndian.Uint16(buf[0:2])
|
||||
temp = int16(buf[2]&0x7f)<<8 + int16(buf[3])
|
||||
// the first bit corresponds to the sign bit
|
||||
if buf[2]&0x80 > 0 {
|
||||
temp = -temp
|
||||
}
|
||||
default:
|
||||
// keeping this for retro-compatibility but not tested
|
||||
hum = binary.LittleEndian.Uint16(buf[0:2])
|
||||
temp = int16(buf[3])<<8 + int16(buf[2]&0x7f)
|
||||
if buf[2]&0x80 > 0 {
|
||||
temp = -temp
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package dht
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDeviceType_extractData(t *testing.T) {
|
||||
bitStr := "0000001010001100000000010101111111101110"
|
||||
buf := bitStringToBytes(bitStr)
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
d DeviceType
|
||||
buf []byte
|
||||
wantTemp int16
|
||||
wantHum uint16
|
||||
}{
|
||||
{
|
||||
// temp = 35.1C hum = 65.2%
|
||||
name: "DHT22", d: DHT22, buf: buf, wantTemp: 351, wantHum: 652,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gotTemp, gotHum := tc.d.extractData(tc.buf)
|
||||
if gotTemp != tc.wantTemp {
|
||||
t.Errorf("extractData() gotTemp = %v, want %v", gotTemp, tc.wantTemp)
|
||||
}
|
||||
if gotHum != tc.wantHum {
|
||||
t.Errorf("extractData() gotHum = %v, want %v", gotHum, tc.wantHum)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func bitStringToBytes(s string) []byte {
|
||||
b := make([]byte, (len(s)+(8-1))/8)
|
||||
for i, r := range s {
|
||||
if r < '0' || r > '1' {
|
||||
panic("not in range")
|
||||
}
|
||||
b[i>>3] |= byte(r-'0') << uint(7-i&7)
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//go:build mimxrt1062 || stm32f405 || atsamd51 || stm32f103xx || k210 || stm32f407
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
// This file provides a definition of the counter for boards with frequency higher than 2^8 ticks per millisecond (>64MHz)
|
||||
type counter uint32
|
||||
@@ -0,0 +1,6 @@
|
||||
//go:build !mimxrt1062 && !stm32f405 && !atsamd51 && !stm32f103xx && !k210 && !stm32f407
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
// This file provides a definition of the counter for boards with frequency lower than 2^8 ticks per millisecond (<64MHz)
|
||||
type counter uint16
|
||||
@@ -0,0 +1,221 @@
|
||||
//go:build tinygo
|
||||
|
||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||
//
|
||||
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
|
||||
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
|
||||
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DummyDevice provides a basic interface for DHT devices.
|
||||
type DummyDevice interface {
|
||||
ReadMeasurements() error
|
||||
Measurements() (temperature int16, humidity uint16, err error)
|
||||
Temperature() (int16, error)
|
||||
TemperatureFloat(scale TemperatureScale) (float32, error)
|
||||
Humidity() (uint16, error)
|
||||
HumidityFloat() (float32, error)
|
||||
}
|
||||
|
||||
// Basic implementation of the DummyDevice
|
||||
// This implementation takes measurements from sensor only with ReadMeasurements function
|
||||
// and does not provide a protection from too frequent calls for measurements.
|
||||
// Since taking measurements from the sensor is time consuming procedure and blocks interrupts,
|
||||
// user can avoid any hidden calls to the sensor.
|
||||
type device struct {
|
||||
pin machine.Pin
|
||||
|
||||
measurements DeviceType
|
||||
initialized bool
|
||||
|
||||
temperature int16
|
||||
humidity uint16
|
||||
}
|
||||
|
||||
// ReadMeasurements reads data from the sensor.
|
||||
// According to documentation pin should be always, but the t *device restores pin to the state before call.
|
||||
func (t *device) ReadMeasurements() error {
|
||||
// initial waiting
|
||||
state := powerUp(t.pin)
|
||||
defer t.pin.Set(state)
|
||||
err := t.read()
|
||||
if err == nil {
|
||||
t.initialized = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Getter for temperature. Temperature method returns temperature as it is sent by device.
|
||||
// The temperature is measured temperature in Celsius multiplied by 10.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) Temperature() (int16, error) {
|
||||
if !t.initialized {
|
||||
return 0, UninitializedDataError
|
||||
}
|
||||
return t.temperature, nil
|
||||
}
|
||||
|
||||
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) TemperatureFloat(scale TemperatureScale) (float32, error) {
|
||||
if !t.initialized {
|
||||
return 0, UninitializedDataError
|
||||
}
|
||||
return scale.convertToFloat(t.temperature), nil
|
||||
}
|
||||
|
||||
// Getter for humidity. Humidity returns humidity as it is sent by device.
|
||||
// The humidity is measured in percentages multiplied by 10.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) Humidity() (uint16, error) {
|
||||
if !t.initialized {
|
||||
return 0, UninitializedDataError
|
||||
}
|
||||
return t.humidity, nil
|
||||
}
|
||||
|
||||
// Getter for humidity. HumidityFloat returns humidity in percentages.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) HumidityFloat() (float32, error) {
|
||||
if !t.initialized {
|
||||
return 0, UninitializedDataError
|
||||
}
|
||||
return float32(t.humidity) / 10., nil
|
||||
}
|
||||
|
||||
// Perform initialization of the communication protocol.
|
||||
// Device lowers the voltage on pin for startingLow=20ms and starts listening for response
|
||||
// Section 5.2 in [1]
|
||||
func initiateCommunication(p machine.Pin) {
|
||||
// Send low signal to the device
|
||||
p.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
p.Low()
|
||||
time.Sleep(startingLow)
|
||||
// Set pin to high and wait for reply
|
||||
p.High()
|
||||
p.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
}
|
||||
|
||||
// Measurements returns both measurements: temperature and humidity as they sent by the device.
|
||||
// If no successful measurements for this device was performed, returns UninitializedDataError.
|
||||
func (t *device) Measurements() (temperature int16, humidity uint16, err error) {
|
||||
if !t.initialized {
|
||||
return 0, 0, UninitializedDataError
|
||||
}
|
||||
temperature = t.temperature
|
||||
humidity = t.humidity
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Main routine that performs communication with the sensor
|
||||
func (t *device) read() error {
|
||||
// initialize loop variables
|
||||
|
||||
// buffer for the data sent by the sensor. Sensor sends 40 bits = 5 bytes
|
||||
bufferData := [5]byte{}
|
||||
buf := bufferData[:]
|
||||
|
||||
// We perform measurements of the signal from the sensor by counting low and high cycles.
|
||||
// The bit is determined by the relative length of the high signal to low signal.
|
||||
// For 1, high signal will be longer than low, for 0---low is longer.
|
||||
// See section 5.3 [1]
|
||||
signalsData := [80]counter{}
|
||||
signals := signalsData[:]
|
||||
|
||||
// Start communication protocol with sensor
|
||||
initiateCommunication(t.pin)
|
||||
// Wait for sensor's response and abort if sensor does not reply
|
||||
err := waitForDataTransmission(t.pin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// count low and high cycles for sensor's reply
|
||||
receiveSignals(t.pin, signals)
|
||||
|
||||
// process received signals and store the result in the buffer. Abort if data transmission was interrupted and not
|
||||
// all 40 bits were received
|
||||
err = t.extractData(signals[:], buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Compute checksum and compare it to the one in data. Abort if checksum is incorrect
|
||||
if !isValid(buf[:]) {
|
||||
return ChecksumError
|
||||
}
|
||||
|
||||
// Extract temperature and humidity data from buffer
|
||||
t.temperature, t.humidity = t.measurements.extractData(buf)
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiveSignals counts number of low and high cycles. The execution is time critical, so the function disables
|
||||
// interrupts
|
||||
func receiveSignals(pin machine.Pin, result []counter) {
|
||||
i := uint8(0)
|
||||
mask := interrupt.Disable()
|
||||
defer interrupt.Restore(mask)
|
||||
for ; i < 40; i++ {
|
||||
result[i*2] = expectChange(pin, false)
|
||||
result[i*2+1] = expectChange(pin, true)
|
||||
}
|
||||
}
|
||||
|
||||
// extractData process signal counters and transforms them into bits.
|
||||
// if any of the bits were not received (timed-out), returns NoDataError
|
||||
func (t *device) extractData(signals []counter, buf []uint8) error {
|
||||
for i := uint8(0); i < 40; i++ {
|
||||
lowCycle := signals[i*2]
|
||||
highCycle := signals[i*2+1]
|
||||
if lowCycle == timeout || highCycle == timeout {
|
||||
return NoDataError
|
||||
}
|
||||
byteN := i >> 3
|
||||
buf[byteN] <<= 1
|
||||
if highCycle > lowCycle {
|
||||
buf[byteN] |= 1
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// waitForDataTransmission waits for reply from the sensor.
|
||||
// If no reply received, returns NoSignalError.
|
||||
// For more details, see section 5.2 in [1]
|
||||
func waitForDataTransmission(p machine.Pin) error {
|
||||
// wait for thermometer to pull down
|
||||
if expectChange(p, true) == timeout {
|
||||
return NoSignalError
|
||||
}
|
||||
//wait for thermometer to pull up
|
||||
if expectChange(p, false) == timeout {
|
||||
return NoSignalError
|
||||
}
|
||||
// wait for thermometer to pull down and start sending the data
|
||||
if expectChange(p, true) == timeout {
|
||||
return NoSignalError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Constructor function for a DummyDevice implementation.
|
||||
// This device provides full control to the user.
|
||||
// It does not do any hidden measurements calls and does not check
|
||||
// for 2 seconds delay between measurements.
|
||||
func NewDummyDevice(pin machine.Pin, deviceType DeviceType) DummyDevice {
|
||||
pin.High()
|
||||
return &device{
|
||||
pin: pin,
|
||||
measurements: deviceType,
|
||||
initialized: false,
|
||||
temperature: 0,
|
||||
humidity: 0,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
//go:build tinygo
|
||||
|
||||
// Package dht provides a driver for DHTXX family temperature and humidity sensors.
|
||||
//
|
||||
// [1] Datasheet DHT11: https://www.mouser.com/datasheet/2/758/DHT11-Technical-Data-Sheet-Translated-Version-1143054.pdf
|
||||
// [2] Datasheet DHT22: https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf
|
||||
// Adafruit C++ driver: https://github.com/adafruit/DHT-sensor-library
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Device interface provides main functionality of the DHTXX sensors.
|
||||
type Device interface {
|
||||
DummyDevice
|
||||
Configure(policy UpdatePolicy)
|
||||
}
|
||||
|
||||
// managedDevice struct provides time control and optional automatic data retrieval from the sensor.
|
||||
// It delegates all the functionality to device
|
||||
type managedDevice struct {
|
||||
t device
|
||||
lastUpdate time.Time
|
||||
policy UpdatePolicy
|
||||
}
|
||||
|
||||
// Measurements returns both measurements: temperature and humidity as they sent by the device.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) Measurements() (temperature int16, humidity uint16, err error) {
|
||||
err = m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return m.t.Measurements()
|
||||
}
|
||||
|
||||
// Getter for temperature. Temperature method returns temperature as it is sent by device.
|
||||
// The temperature is measured temperature in Celsius multiplied by 10.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) Temperature() (temp int16, err error) {
|
||||
err = m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
temp, err = m.t.Temperature()
|
||||
return
|
||||
}
|
||||
|
||||
func (m *managedDevice) checkForUpdateOnDataRequest() (err error) {
|
||||
// update if necessary
|
||||
if m.policy.UpdateAutomatically {
|
||||
err = m.ReadMeasurements()
|
||||
}
|
||||
// ignore error if the data was updated recently
|
||||
// interface comparison does not work in tinygo. Therefore need to cast to explicit type
|
||||
if code, ok := err.(ErrorCode); ok && code == UpdateError {
|
||||
err = nil
|
||||
}
|
||||
// add error if the data is not initialized
|
||||
if !m.t.initialized {
|
||||
err = UninitializedDataError
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Getter for temperature. TemperatureFloat returns temperature in a given scale.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) TemperatureFloat(scale TemperatureScale) (float32, error) {
|
||||
err := m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return m.t.TemperatureFloat(scale)
|
||||
}
|
||||
|
||||
// Getter for humidity. Humidity returns humidity as it is sent by device.
|
||||
// The humidity is measured in percentages multiplied by 10.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) Humidity() (hum uint16, err error) {
|
||||
err = m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return m.t.Humidity()
|
||||
}
|
||||
|
||||
// Getter for humidity. HumidityFloat returns humidity in percentages.
|
||||
// Depending on the UpdatePolicy of the device may update cached measurements.
|
||||
func (m *managedDevice) HumidityFloat() (float32, error) {
|
||||
err := m.checkForUpdateOnDataRequest()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return m.t.HumidityFloat()
|
||||
}
|
||||
|
||||
// ReadMeasurements reads data from the sensor.
|
||||
// The function will return UpdateError if it is called more frequently than specified in UpdatePolicy
|
||||
func (m *managedDevice) ReadMeasurements() (err error) {
|
||||
timestamp := time.Now()
|
||||
if !m.t.initialized || timestamp.Sub(m.lastUpdate) > m.policy.UpdateTime {
|
||||
err = m.t.ReadMeasurements()
|
||||
} else {
|
||||
err = UpdateError
|
||||
}
|
||||
if err == nil {
|
||||
m.lastUpdate = timestamp
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Configure configures UpdatePolicy for Device.
|
||||
// Configure checks for policy.UpdateTime and prevent from updating more frequently than specified in [1][2]
|
||||
// to prevent undefined behaviour of the sensor.
|
||||
func (m *managedDevice) Configure(policy UpdatePolicy) {
|
||||
if policy.UpdateAutomatically && policy.UpdateTime < time.Second*2 {
|
||||
policy.UpdateTime = time.Second * 2
|
||||
}
|
||||
m.policy = policy
|
||||
}
|
||||
|
||||
// Constructor of the Device implementation.
|
||||
// This implementation updates data every 2 seconds during data access.
|
||||
func New(pin machine.Pin, deviceType DeviceType) Device {
|
||||
pin.High()
|
||||
return &managedDevice{
|
||||
t: device{
|
||||
pin: pin,
|
||||
measurements: deviceType,
|
||||
initialized: false,
|
||||
},
|
||||
lastUpdate: time.Time{},
|
||||
policy: UpdatePolicy{
|
||||
UpdateTime: time.Second * 2,
|
||||
UpdateAutomatically: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Constructor of the Device implementation with given UpdatePolicy
|
||||
func NewWithPolicy(pin machine.Pin, deviceType DeviceType, updatePolicy UpdatePolicy) Device {
|
||||
pin.High()
|
||||
result := &managedDevice{
|
||||
t: device{
|
||||
pin: pin,
|
||||
measurements: deviceType,
|
||||
initialized: false,
|
||||
},
|
||||
lastUpdate: time.Time{},
|
||||
}
|
||||
result.Configure(updatePolicy)
|
||||
return result
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
//go:build tinygo
|
||||
|
||||
package dht // import "tinygo.org/x/drivers/dht"
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Check if the pin is disabled
|
||||
func powerUp(p machine.Pin) bool {
|
||||
state := p.Get()
|
||||
if !state {
|
||||
p.High()
|
||||
time.Sleep(startTimeout)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func expectChange(p machine.Pin, oldState bool) counter {
|
||||
cnt := counter(0)
|
||||
for ; p.Get() == oldState && cnt != timeout; cnt++ {
|
||||
}
|
||||
return cnt
|
||||
}
|
||||
|
||||
func checksum(buf []uint8) uint8 {
|
||||
return buf[4]
|
||||
}
|
||||
func computeChecksum(buf []uint8) uint8 {
|
||||
return buf[0] + buf[1] + buf[2] + buf[3]
|
||||
}
|
||||
|
||||
func isValid(buf []uint8) bool {
|
||||
return checksum(buf) == computeChecksum(buf)
|
||||
}
|
||||
@@ -12,3 +12,19 @@ type Displayer interface {
|
||||
// Display sends the buffer (if any) to the screen.
|
||||
Display() error
|
||||
}
|
||||
|
||||
// Rotation is how much a display has been rotated. Displays can be rotated, and
|
||||
// sometimes also mirrored.
|
||||
type Rotation uint8
|
||||
|
||||
// Clockwise rotation of the screen.
|
||||
const (
|
||||
Rotation0 = iota
|
||||
Rotation90
|
||||
Rotation180
|
||||
Rotation270
|
||||
Rotation0Mirror
|
||||
Rotation90Mirror
|
||||
Rotation180Mirror
|
||||
Rotation270Mirror
|
||||
)
|
||||
|
||||
+7
-8
@@ -3,22 +3,22 @@
|
||||
//
|
||||
// Here is an example in TinyGo that uses the BMP180 digital barometer:
|
||||
//
|
||||
// package main
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// import (
|
||||
// "time"
|
||||
// "machine"
|
||||
//
|
||||
// "tinygo.org/x/drivers/bmp180"
|
||||
// )
|
||||
// "tinygo.org/x/drivers/bmp180"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// func main() {
|
||||
// machine.I2C0.Configure(machine.I2CConfig{})
|
||||
// sensor := bmp180.New(machine.I2C0)
|
||||
// sensor.Configure()
|
||||
//
|
||||
// connected := sensor.Connected()
|
||||
// if !connected {
|
||||
// connected := sensor.Connected()
|
||||
// if !connected {
|
||||
// println("BMP180 not detected")
|
||||
// return
|
||||
// }
|
||||
@@ -38,5 +38,4 @@
|
||||
// Each individual driver is contained within its own sub-package within this package and
|
||||
// there are no interdependencies in order to minimize the final size of compiled code that
|
||||
// uses any of these drivers.
|
||||
//
|
||||
package drivers // import "tinygo.org/x/drivers"
|
||||
|
||||
+7
-7
@@ -2,7 +2,6 @@
|
||||
//
|
||||
// Datasheet:
|
||||
// https://datasheets.maximintegrated.com/en/ds/DS1307.pdf
|
||||
//
|
||||
package ds1307 // import "tinygo.org/x/drivers/ds1307"
|
||||
|
||||
import (
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/legacy"
|
||||
)
|
||||
|
||||
// Device wraps an I2C connection to a DS1307 device.
|
||||
@@ -42,10 +42,10 @@ func (d *Device) SetTime(t time.Time) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Time returns the time and date
|
||||
func (d *Device) Time() (time.Time, error) {
|
||||
// ReadTime returns the date and time
|
||||
func (d *Device) ReadTime() (time.Time, error) {
|
||||
data := make([]byte, 8)
|
||||
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
|
||||
err := legacy.ReadRegister(d.bus, d.Address, uint8(TimeDate), data)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
@@ -106,7 +106,7 @@ func (d *Device) Read(data []uint8) (n int, err error) {
|
||||
if int(d.AddressSRAM)+len(data)-1 > SRAMEndAddress {
|
||||
return 0, errors.New("EOF")
|
||||
}
|
||||
err = d.bus.ReadRegister(d.Address, d.AddressSRAM, data)
|
||||
err = legacy.ReadRegister(d.bus, d.Address, d.AddressSRAM, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func (d *Device) SetOscillatorFrequency(sqw uint8) error {
|
||||
// IsOscillatorRunning returns if the oscillator is running
|
||||
func (d *Device) IsOscillatorRunning() bool {
|
||||
data := []byte{0}
|
||||
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
|
||||
err := legacy.ReadRegister(d.bus, d.Address, uint8(TimeDate), data)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func (d *Device) IsOscillatorRunning() bool {
|
||||
// SetOscillatorRunning starts/stops internal oscillator by toggling halt bit
|
||||
func (d *Device) SetOscillatorRunning(running bool) error {
|
||||
data := make([]byte, 3)
|
||||
err := d.bus.ReadRegister(d.Address, uint8(TimeDate), data)
|
||||
err := legacy.ReadRegister(d.bus, d.Address, uint8(TimeDate), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// Package ds18b20 provides a driver for the DS18B20 digital thermometer
|
||||
//
|
||||
// Datasheet:
|
||||
// https://www.analog.com/media/en/technical-documentation/data-sheets/DS18B20.pdf
|
||||
package ds18b20 // import "tinygo.org/x/drivers/ds18b20"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Device ROM commands
|
||||
const (
|
||||
CONVERT_TEMPERATURE uint8 = 0x44
|
||||
READ_SCRATCHPAD uint8 = 0xBE
|
||||
WRITE_SCRATCHPAD uint8 = 0x4E
|
||||
)
|
||||
|
||||
type OneWireDevice interface {
|
||||
Write(uint8)
|
||||
Read() uint8
|
||||
Select([]uint8) error
|
||||
Сrc8([]uint8) uint8
|
||||
}
|
||||
|
||||
// Device wraps a connection to an 1-Wire devices.
|
||||
type Device struct {
|
||||
owd OneWireDevice
|
||||
}
|
||||
|
||||
// Errors list
|
||||
var (
|
||||
errReadTemperature = errors.New("Error: DS18B20. Read temperature error: CRC mismatch.")
|
||||
)
|
||||
|
||||
func New(owd OneWireDevice) Device {
|
||||
return Device{
|
||||
owd: owd,
|
||||
}
|
||||
}
|
||||
|
||||
// Configure. Initializes the device, left for compatibility reasons.
|
||||
func (d Device) Configure() {}
|
||||
|
||||
// ThermometerResolution sets thermometer resolution from 9 to 12 bits
|
||||
func (d Device) ThermometerResolution(romid []uint8, resolution uint8) {
|
||||
if 9 <= resolution && resolution <= 12 {
|
||||
d.owd.Select(romid)
|
||||
d.owd.Write(WRITE_SCRATCHPAD) // send three data bytes to scratchpad (TH, TL, and config)
|
||||
d.owd.Write(0xFF) // to TH
|
||||
d.owd.Write(0x00) // to TL
|
||||
d.owd.Write(((resolution - 9) << 5) | 0x1F) // to resolution config
|
||||
}
|
||||
}
|
||||
|
||||
// RequestTemperature sends request to device
|
||||
func (d Device) RequestTemperature(romid []uint8) {
|
||||
d.owd.Select(romid)
|
||||
d.owd.Write(CONVERT_TEMPERATURE)
|
||||
}
|
||||
|
||||
// ReadTemperatureRaw returns the raw temperature.
|
||||
// ScratchPad memory map:
|
||||
// byte 0: Temperature LSB
|
||||
// byte 1: Temperature MSB
|
||||
func (d Device) ReadTemperatureRaw(romid []uint8) ([]uint8, error) {
|
||||
spb := make([]uint8, 9) // ScratchPad buffer
|
||||
d.owd.Select(romid)
|
||||
d.owd.Write(READ_SCRATCHPAD)
|
||||
for i := 0; i < 9; i++ {
|
||||
spb[i] = d.owd.Read()
|
||||
}
|
||||
if d.owd.Сrc8(spb) != 0 {
|
||||
return nil, errReadTemperature
|
||||
}
|
||||
return spb[:2:2], nil
|
||||
}
|
||||
|
||||
// ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
|
||||
func (d Device) ReadTemperature(romid []uint8) (int32, error) {
|
||||
raw, err := d.ReadTemperatureRaw(romid)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
t := int32(uint16(raw[0]) | uint16(raw[1])<<8)
|
||||
if t&0x8000 == 0x8000 {
|
||||
t -= 0x10000
|
||||
}
|
||||
return (t * 625 / 10), nil
|
||||
}
|
||||
+321
-34
@@ -5,9 +5,12 @@
|
||||
package ds3231 // import "tinygo.org/x/drivers/ds3231"
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/regmap"
|
||||
)
|
||||
|
||||
type Mode uint8
|
||||
@@ -16,6 +19,7 @@ type Mode uint8
|
||||
type Device struct {
|
||||
bus drivers.I2C
|
||||
Address uint16
|
||||
d regmap.Device8I2C
|
||||
}
|
||||
|
||||
// New creates a new DS3231 connection. The I2C bus must already be
|
||||
@@ -23,75 +27,83 @@ type Device struct {
|
||||
//
|
||||
// This function only creates the Device object, it does not touch the device.
|
||||
func New(bus drivers.I2C) Device {
|
||||
return Device{
|
||||
d := Device{
|
||||
bus: bus,
|
||||
Address: Address,
|
||||
}
|
||||
d.Configure()
|
||||
return d
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication
|
||||
func (d *Device) Configure() bool {
|
||||
d.d.SetBus(d.bus, d.Address, binary.BigEndian)
|
||||
return true
|
||||
}
|
||||
|
||||
// IsTimeValid return true/false is the time in the device is valid
|
||||
func (d *Device) IsTimeValid() bool {
|
||||
data := []byte{0}
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_STATUS, data)
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (data[0] & (1 << OSF)) == 0x00
|
||||
return (status & (1 << OSF)) == 0x00
|
||||
}
|
||||
|
||||
// IsRunning returns if the oscillator is running
|
||||
func (d *Device) IsRunning() bool {
|
||||
data := []uint8{0}
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_CONTROL, data)
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (data[0] & (1 << EOSC)) == 0x00
|
||||
return (control & (1 << EOSC)) == 0x00
|
||||
}
|
||||
|
||||
// SetRunning starts the internal oscillator
|
||||
func (d *Device) SetRunning(isRunning bool) error {
|
||||
data := []uint8{0}
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_CONTROL, data)
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isRunning {
|
||||
data[0] &^= uint8(1 << EOSC)
|
||||
control &^= uint8(1 << EOSC)
|
||||
} else {
|
||||
data[0] |= 1 << EOSC
|
||||
control |= 1 << EOSC
|
||||
}
|
||||
err = d.bus.WriteRegister(uint8(d.Address), REG_CONTROL, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
// SetTime sets the date and time in the DS3231
|
||||
// SetTime sets the date and time in the DS3231. The DS3231 hardware supports
|
||||
// only a 2-digit year field, so the current year will be stored as an offset
|
||||
// from the year 2000, which supports the year 2000 until 2100.
|
||||
//
|
||||
// The DS3231 also supports a one-bit 'century' flag which is set by the chip
|
||||
// when the year field rolls over from 99 to 00. The current code interprets
|
||||
// this flag to be the year 2100, which appears to extend the range of years
|
||||
// until the year 2200. However the DS3231 does not incorporate the 'century'
|
||||
// flag in its leap year calculation, so it will incorrectly identify the year
|
||||
// 2100 as a leap year, causing it to increment from 2100-02-28 to 2100-02-29
|
||||
// instead of 2100-03-01.
|
||||
func (d *Device) SetTime(dt time.Time) error {
|
||||
data := []byte{0}
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_STATUS, data)
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data[0] &^= 1 << OSF
|
||||
err = d.bus.WriteRegister(uint8(d.Address), REG_STATUS, data)
|
||||
if err != nil {
|
||||
status &^= 1 << OSF
|
||||
if err = d.d.Write8(REG_STATUS, status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data = make([]uint8, 7)
|
||||
data := make([]uint8, 7)
|
||||
data[0] = uint8ToBCD(uint8(dt.Second()))
|
||||
data[1] = uint8ToBCD(uint8(dt.Minute()))
|
||||
data[2] = uint8ToBCD(uint8(dt.Hour()))
|
||||
|
||||
year := uint8(dt.Year() - 2000)
|
||||
// This code interprets the centuryFlag to be the year 2100. Warning: The
|
||||
// DS3231 does not incorporate the centuryFlag in its leap year calculation.
|
||||
// It will increment from 2100-02-28 to 2100-02-29, which is incorrect because
|
||||
// the year 2100 is not a leap year in the Gregorian calendar.
|
||||
centuryFlag := uint8(0)
|
||||
if year >= 100 {
|
||||
year -= 100
|
||||
@@ -103,21 +115,16 @@ func (d *Device) SetTime(dt time.Time) error {
|
||||
data[5] = uint8ToBCD(uint8(dt.Month()) | centuryFlag)
|
||||
data[6] = uint8ToBCD(year)
|
||||
|
||||
err = d.bus.WriteRegister(uint8(d.Address), REG_TIMEDATE, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return d.bus.Tx(d.Address, append([]byte{REG_TIMEDATE}, data...), nil)
|
||||
}
|
||||
|
||||
// ReadTime returns the date and time
|
||||
func (d *Device) ReadTime() (dt time.Time, err error) {
|
||||
data := make([]uint8, 7)
|
||||
err = d.bus.ReadRegister(uint8(d.Address), REG_TIMEDATE, data)
|
||||
if err != nil {
|
||||
if err = d.d.ReadData(REG_TIMEDATE, data); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
second := bcdToInt(data[0] & 0x7F)
|
||||
minute := bcdToInt(data[1])
|
||||
hour := hoursBCDToInt(data[2])
|
||||
@@ -135,12 +142,284 @@ func (d *Device) ReadTime() (dt time.Time, err error) {
|
||||
|
||||
// ReadTemperature returns the temperature in millicelsius (mC)
|
||||
func (d *Device) ReadTemperature() (int32, error) {
|
||||
data := make([]uint8, 2)
|
||||
err := d.bus.ReadRegister(uint8(d.Address), REG_TEMP, data)
|
||||
temp, err := d.d.Read16(REG_TEMP)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int32(data[0])*1000 + int32((data[1]>>6)*25)*10, nil
|
||||
return milliCelsius(temp), nil
|
||||
}
|
||||
|
||||
// GetSqwPinMode returns the current square wave output frequency
|
||||
func (d *Device) GetSqwPinMode() SqwPinMode {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return SQW_OFF
|
||||
}
|
||||
|
||||
control &= 0x1C // turn off INTCON
|
||||
if control&0x04 != 0 {
|
||||
return SQW_OFF
|
||||
}
|
||||
|
||||
return SqwPinMode(control)
|
||||
}
|
||||
|
||||
// SetSqwPinMode sets the square wave output mode to the given frequency
|
||||
func (d *Device) SetSqwPinMode(mode SqwPinMode) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
control &^= 0x04 // turn off INTCON
|
||||
control &^= 0x18 // set freq bits to 0
|
||||
|
||||
control |= uint8(mode)
|
||||
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
// SetAlarm1 sets alarm1 to the given time and mode
|
||||
func (d *Device) SetAlarm1(dt time.Time, mode Alarm1Mode) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if control&(1<<INTCN) == 0x00 {
|
||||
return errors.New("INTCN has to be disabled")
|
||||
}
|
||||
|
||||
A1M1 := uint8((mode & 0x01) << 7)
|
||||
A1M2 := uint8((mode & 0x02) << 6)
|
||||
A1M3 := uint8((mode & 0x04) << 5)
|
||||
A1M4 := uint8((mode & 0x08) << 4)
|
||||
DY_DT := uint8((mode & 0x10) << 2)
|
||||
|
||||
day := dt.Day()
|
||||
if DY_DT > 0 {
|
||||
day = dowToDS3231(int(dt.Weekday()))
|
||||
}
|
||||
|
||||
alarm1 := uint32(uint8ToBCD(uint8(dt.Second()))|A1M1) << 24
|
||||
alarm1 |= uint32(uint8ToBCD(uint8(dt.Minute()))|A1M2) << 16
|
||||
alarm1 |= uint32(uint8ToBCD(uint8(dt.Hour()))|A1M3) << 8
|
||||
alarm1 |= uint32(uint8ToBCD(uint8(day)) | A1M4 | DY_DT)
|
||||
|
||||
if err := d.d.Write32(REG_ALARMONE, alarm1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
control |= AlarmFlag_Alarm1
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
// ReadAlarm1 returns the alarm1 time
|
||||
func (d *Device) ReadAlarm1() (dt time.Time, err error) {
|
||||
data := make([]uint8, 4)
|
||||
if err = d.d.ReadData(REG_ALARMONE, data); err != nil {
|
||||
return
|
||||
}
|
||||
second := bcdToInt(data[0] & 0x7F)
|
||||
minute := bcdToInt(data[1] & 0x7F)
|
||||
hour := hoursBCDToInt(data[2] & 0x3F)
|
||||
|
||||
isDayOfWeek := (data[3] & 0x40) >> 6
|
||||
var day int
|
||||
if isDayOfWeek > 0 {
|
||||
day = bcdToInt(data[3] & 0x0F)
|
||||
} else {
|
||||
day = bcdToInt(data[3] & 0x3F)
|
||||
}
|
||||
|
||||
dt = time.Date(2000, 5, day, hour, minute, second, 0, time.UTC)
|
||||
return
|
||||
}
|
||||
|
||||
// SetAlarm2 sets alarm2 to the given time and mode
|
||||
func (d *Device) SetAlarm2(dt time.Time, mode Alarm2Mode) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if control&(1<<INTCN) == 0x00 {
|
||||
return errors.New("INTCN has to be disabled")
|
||||
}
|
||||
|
||||
A2M2 := uint8((mode & 0x01) << 7)
|
||||
A2M3 := uint8((mode & 0x02) << 6)
|
||||
A2M4 := uint8((mode & 0x04) << 5)
|
||||
DY_DT := uint8((mode & 0x08) << 3)
|
||||
|
||||
day := dt.Day()
|
||||
if DY_DT > 0 {
|
||||
day = dowToDS3231(int(dt.Weekday()))
|
||||
}
|
||||
|
||||
data := make([]uint8, 4)
|
||||
data[0] = uint8ToBCD(uint8(dt.Minute())) | A2M2
|
||||
data[1] = uint8ToBCD(uint8(dt.Hour())) | A2M3
|
||||
data[2] = uint8ToBCD(uint8(day)) | A2M4 | DY_DT
|
||||
if err = d.bus.Tx(d.Address, append([]byte{REG_ALARMTWO}, data...), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
control |= AlarmFlag_Alarm2
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
// ReadAlarm2 returns the alarm2 time
|
||||
func (d *Device) ReadAlarm2() (dt time.Time, err error) {
|
||||
data := make([]uint8, 3)
|
||||
if err = d.d.ReadData(REG_ALARMTWO, data); err != nil {
|
||||
return
|
||||
}
|
||||
minute := bcdToInt(data[0] & 0x7F)
|
||||
hour := hoursBCDToInt(data[1] & 0x3F)
|
||||
|
||||
isDayOfWeek := (data[2] & 0x40) >> 6
|
||||
var day int
|
||||
if isDayOfWeek > 0 {
|
||||
day = bcdToInt(data[2] & 0x0F)
|
||||
} else {
|
||||
day = bcdToInt(data[2] & 0x3F)
|
||||
}
|
||||
|
||||
dt = time.Date(2000, 5, day, hour, minute, 0, 0, time.UTC)
|
||||
return
|
||||
}
|
||||
|
||||
// IsEnabledAlarm1 returns true when alarm1 is enabled
|
||||
func (d *Device) IsEnabledAlarm1() bool {
|
||||
return d.isEnabledAlarm(1)
|
||||
}
|
||||
|
||||
// SetEnabledAlarm1 sets the enabled status of alarm1
|
||||
func (d *Device) SetEnabledAlarm1(enable bool) error {
|
||||
if enable {
|
||||
return d.enableAlarm(1)
|
||||
}
|
||||
return d.disableAlarm(1)
|
||||
}
|
||||
|
||||
// IsEnabledAlarm2 returns true when alarm2 is enabled
|
||||
func (d *Device) IsEnabledAlarm2() bool {
|
||||
return d.isEnabledAlarm(2)
|
||||
}
|
||||
|
||||
// SetEnabledAlarm2 sets the enabled status of alarm2
|
||||
func (d *Device) SetEnabledAlarm2(enable bool) error {
|
||||
if enable {
|
||||
return d.enableAlarm(2)
|
||||
}
|
||||
return d.disableAlarm(2)
|
||||
}
|
||||
|
||||
// ClearAlarm1 clears status of alarm1
|
||||
func (d *Device) ClearAlarm1() error {
|
||||
return d.clearAlarm(1)
|
||||
}
|
||||
|
||||
// ClearAlarm2 clears status of alarm2
|
||||
func (d *Device) ClearAlarm2() error {
|
||||
return d.clearAlarm(2)
|
||||
}
|
||||
|
||||
// IsAlarm1Fired returns true when alarm1 is firing
|
||||
func (d *Device) IsAlarm1Fired() bool {
|
||||
return d.isAlarmFired(1)
|
||||
}
|
||||
|
||||
// IsAlarm2Fired returns true when alarm2 is firing
|
||||
func (d *Device) IsAlarm2Fired() bool {
|
||||
return d.isAlarmFired(2)
|
||||
}
|
||||
|
||||
// SetEnabled32K sets the enabled status of the 32KHz output
|
||||
func (d *Device) SetEnabled32K(enable bool) error {
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if enable {
|
||||
status |= 1 << EN32KHZ
|
||||
} else {
|
||||
status &^= 1 << EN32KHZ
|
||||
}
|
||||
|
||||
return d.d.Write8(REG_STATUS, status)
|
||||
}
|
||||
|
||||
// IsEnabled32K returns true when the 32KHz output is enabled
|
||||
func (d *Device) IsEnabled32K() bool {
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (status & (1 << EN32KHZ)) != 0x00
|
||||
}
|
||||
|
||||
func (d *Device) disableAlarm(alarm_num uint8) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
control &^= (1 << (alarm_num - 1))
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
func (d *Device) enableAlarm(alarm_num uint8) error {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
control |= (1 << (alarm_num - 1))
|
||||
return d.d.Write8(REG_CONTROL, control)
|
||||
}
|
||||
|
||||
func (d *Device) isEnabledAlarm(alarm_num uint8) bool {
|
||||
control, err := d.d.Read8(REG_CONTROL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (control & (1 << (alarm_num - 1))) != 0x00
|
||||
}
|
||||
|
||||
func (d *Device) clearAlarm(alarm_num uint8) error {
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status &^= (1 << (alarm_num - 1))
|
||||
return d.d.Write8(REG_STATUS, status)
|
||||
}
|
||||
|
||||
func (d *Device) isAlarmFired(alarm_num uint8) bool {
|
||||
status, err := d.d.Read8(REG_STATUS)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (status & (1 << (alarm_num - 1))) != 0x00
|
||||
}
|
||||
|
||||
// milliCelsius converts the raw temperature bytes (msb and lsb) from the DS3231
|
||||
// into a 32-bit signed integer in units of milli Celsius (1/1000 deg C).
|
||||
//
|
||||
// According to the DS3231 datasheet: "Temperature is represented as a 10-bit
|
||||
// code with a resolution of 0.25 deg C and is accessible at location 11h and
|
||||
// 12h. The temperature is encoded in two's complement format. The upper 8 bits,
|
||||
// the integer portion, are at location 11h and the lower 2 bits, the fractional
|
||||
// portion, are in the upper nibble at location 12h."
|
||||
//
|
||||
// In other words, the msb and lsb bytes should be treated as a signed 16-bit
|
||||
// integer in units of (1/256 deg C). It is possible to convert this into a
|
||||
// 16-bit signed integer in units of centi Celsius (1/100 deg C) with no loss of
|
||||
// precision or dynamic range. But for backwards compatibility, let's instead
|
||||
// convert this into a 32-bit signed integer in units of milli Celsius.
|
||||
func milliCelsius(tempBytes uint16) int32 {
|
||||
t256 := int16(uint16(tempBytes>>8)<<8 | uint16(tempBytes&0xFF))
|
||||
t1000 := int32(t256) / 64 * 250
|
||||
return t1000
|
||||
}
|
||||
|
||||
// uint8ToBCD converts a byte to BCD for the DS3231
|
||||
@@ -165,3 +444,11 @@ func hoursBCDToInt(value uint8) (hour int) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// dowToDS3231 converts the day of the week to internal DS3231 format
|
||||
func dowToDS3231(d int) int {
|
||||
if d == 0 {
|
||||
return 7
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package ds3231
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPositiveMilliCelsius(t *testing.T) {
|
||||
t1000 := milliCelsius(0)
|
||||
if t1000 != 0 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b0000000001000000)
|
||||
if t1000 != 250 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b0000000010000000)
|
||||
if t1000 != 500 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b0000000011000000)
|
||||
if t1000 != 750 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b0000000100000000)
|
||||
if t1000 != 1000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b0000001000000000)
|
||||
if t1000 != 2000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
// highest temperature is 127.750C
|
||||
t1000 = milliCelsius(0b0111111111000000)
|
||||
if t1000 != 127750 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegativeMilliCelsius(t *testing.T) {
|
||||
t1000 := milliCelsius(0b1111111111000000)
|
||||
if t1000 != -250 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b1111111110000000)
|
||||
if t1000 != -500 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b1111111101000000)
|
||||
if t1000 != -750 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b1111111100000000)
|
||||
if t1000 != -1000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
t1000 = milliCelsius(0b1111111000000000)
|
||||
if t1000 != -2000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
|
||||
// lowest temperature is -128.000C
|
||||
t1000 = milliCelsius(0b1000000000000000)
|
||||
if t1000 != -128000 {
|
||||
t.Fatal(t1000)
|
||||
}
|
||||
}
|
||||
@@ -46,3 +46,52 @@ const (
|
||||
AlarmTwo Mode = 4
|
||||
ModeAlarmBoth Mode = 5
|
||||
)
|
||||
|
||||
// SQW Pin Modes
|
||||
type SqwPinMode uint8
|
||||
|
||||
const (
|
||||
SQW_OFF SqwPinMode = 0x1C
|
||||
SQW_1HZ SqwPinMode = 0x00
|
||||
SQW_1KHZ SqwPinMode = 0x08
|
||||
SQW_4KHZ SqwPinMode = 0x10
|
||||
SQW_8KHZ SqwPinMode = 0x18
|
||||
)
|
||||
|
||||
// Alarm1 Modes define which parts of the set alarm time has to match the current timestamp of the clock device for
|
||||
// alarm1 to fire
|
||||
type Alarm1Mode uint8
|
||||
|
||||
const (
|
||||
// Alarm1 fires every second
|
||||
A1_PER_SECOND Alarm1Mode = 0x0F
|
||||
// Alarm1 fires when the seconds match
|
||||
A1_SECOND Alarm1Mode = 0x0E
|
||||
// Alarm1 fires when both seconds and minutes match
|
||||
A1_MINUTE Alarm1Mode = 0x0C
|
||||
// Alarm1 fires when seconds, minutes and hours match
|
||||
A1_HOUR Alarm1Mode = 0x08
|
||||
// Alarm1 fires when seconds, minutes, hours and the day of the month match
|
||||
A1_DATE Alarm1Mode = 0x00
|
||||
// Alarm1 fires when seconds, minutes, hours and the day of the week match
|
||||
A1_DAY Alarm1Mode = 0x10
|
||||
)
|
||||
|
||||
// Alarm2 Modes define which parts of the set alarm time has to match the current timestamp of the clock device for
|
||||
// alarm2 to fire.
|
||||
//
|
||||
// Alarm2 only supports matching down to the minute unlike alarm1 which supports matching down to the second.
|
||||
type Alarm2Mode uint8
|
||||
|
||||
const (
|
||||
// Alarm2 fires every minute
|
||||
A2_PER_MINUTE Alarm2Mode = 0x07
|
||||
// Alarm2 fires when the minutes match
|
||||
A2_MINUTE Alarm2Mode = 0x06
|
||||
// Alarm2 fires when both minutes and hours match
|
||||
A2_HOUR Alarm2Mode = 0x04
|
||||
// Alarm2 fires when minutes, hours and the day of the month match
|
||||
A2_DATE Alarm2Mode = 0x00
|
||||
// Alarm2 fires when minutes, hours and the day of the week match
|
||||
A2_DAY Alarm2Mode = 0x08
|
||||
)
|
||||
|
||||
+138
-22
@@ -2,28 +2,76 @@
|
||||
package easystepper // import "tinygo.org/x/drivers/easystepper"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StepMode determines the coil sequence used to perform a single step
|
||||
type StepMode uint8
|
||||
|
||||
// Valid values for StepMode
|
||||
const (
|
||||
// ModeFour uses a 'four step' coil sequence (12-23-34-41). This is the default (zero-value) mode
|
||||
ModeFour StepMode = iota
|
||||
// ModeEight uses an 'eight step' coil sequence (1-12-2-23-3-34-4-41)
|
||||
ModeEight
|
||||
)
|
||||
|
||||
// stepCount is a helper function to return the number of steps in a StepMode sequence
|
||||
func (sm StepMode) stepCount() uint {
|
||||
switch sm {
|
||||
default:
|
||||
fallthrough
|
||||
case ModeFour:
|
||||
return 4
|
||||
case ModeEight:
|
||||
return 8
|
||||
}
|
||||
}
|
||||
|
||||
// DeviceConfig contains the configuration data for a single easystepper driver
|
||||
type DeviceConfig struct {
|
||||
// Pin1 ... Pin4 determines the pins to configure and use for the device
|
||||
Pin1, Pin2, Pin3, Pin4 machine.Pin
|
||||
// StepCount is the number of steps required to perform a full revolution of the stepper motor
|
||||
StepCount uint
|
||||
// RPM determines the speed of the stepper motor in 'Revolutions per Minute'
|
||||
RPM uint
|
||||
// Mode determines the coil sequence used to perform a single step
|
||||
Mode StepMode
|
||||
}
|
||||
|
||||
// DualDeviceConfig contains the configuration data for a dual easystepper driver
|
||||
type DualDeviceConfig struct {
|
||||
DeviceConfig
|
||||
// Pin5 ... Pin8 determines the pins to configure and use for the second device
|
||||
Pin5, Pin6, Pin7, Pin8 machine.Pin
|
||||
}
|
||||
|
||||
// Device holds the pins and the delay between steps
|
||||
type Device struct {
|
||||
pins [4]machine.Pin
|
||||
stepDelay int32
|
||||
stepDelay time.Duration
|
||||
stepNumber uint8
|
||||
stepMode StepMode
|
||||
}
|
||||
|
||||
// DualDevice holds information for controlling 2 motors
|
||||
type DualDevice struct {
|
||||
devices [2]Device
|
||||
devices [2]*Device
|
||||
}
|
||||
|
||||
// New returns a new easystepper driver given 4 pins, number of steps and rpm
|
||||
func New(pin1, pin2, pin3, pin4 machine.Pin, steps int32, rpm int32) Device {
|
||||
return Device{
|
||||
pins: [4]machine.Pin{pin1, pin2, pin3, pin4},
|
||||
stepDelay: 60000000 / (steps * rpm),
|
||||
// New returns a new single easystepper driver given a DeviceConfig
|
||||
func New(config DeviceConfig) (*Device, error) {
|
||||
if config.StepCount == 0 || config.RPM == 0 {
|
||||
return nil, errors.New("config.StepCount and config.RPM must be > 0")
|
||||
}
|
||||
return &Device{
|
||||
pins: [4]machine.Pin{config.Pin1, config.Pin2, config.Pin3, config.Pin4},
|
||||
stepDelay: time.Second * 60 / time.Duration((config.StepCount * config.RPM)),
|
||||
stepMode: config.Mode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Configure configures the pins of the Device
|
||||
@@ -34,17 +82,23 @@ func (d *Device) Configure() {
|
||||
}
|
||||
|
||||
// NewDual returns a new dual easystepper driver given 8 pins, number of steps and rpm
|
||||
func NewDual(pin1, pin2, pin3, pin4, pin5, pin6, pin7, pin8 machine.Pin, steps int32, rpm int32) DualDevice {
|
||||
var dual DualDevice
|
||||
dual.devices[0] = Device{
|
||||
pins: [4]machine.Pin{pin1, pin2, pin3, pin4},
|
||||
stepDelay: 60000000 / (steps * rpm),
|
||||
func NewDual(config DualDeviceConfig) (*DualDevice, error) {
|
||||
// Create the first device
|
||||
dev1, err := New(config.DeviceConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dual.devices[1] = Device{
|
||||
pins: [4]machine.Pin{pin5, pin6, pin7, pin8},
|
||||
stepDelay: 60000000 / (steps * rpm),
|
||||
// Create the second device
|
||||
config.DeviceConfig.Pin1 = config.Pin5
|
||||
config.DeviceConfig.Pin2 = config.Pin6
|
||||
config.DeviceConfig.Pin3 = config.Pin7
|
||||
config.DeviceConfig.Pin4 = config.Pin8
|
||||
dev2, err := New(config.DeviceConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dual
|
||||
// Return composite dual device
|
||||
return &DualDevice{devices: [2]*Device{dev1, dev2}}, nil
|
||||
}
|
||||
|
||||
// Configure configures the pins of the DualDevice
|
||||
@@ -64,7 +118,7 @@ func (d *Device) Move(steps int32) {
|
||||
var s int32
|
||||
d.stepMotor(d.stepNumber)
|
||||
for s = int32(d.stepNumber); s < steps; s++ {
|
||||
time.Sleep(time.Duration(d.stepDelay) * time.Microsecond)
|
||||
time.Sleep(d.stepDelay)
|
||||
d.moveDirectionSteps(direction, s)
|
||||
}
|
||||
}
|
||||
@@ -101,7 +155,7 @@ func (d *DualDevice) Move(stepsA, stepsB int32) {
|
||||
stepsA += int32(d.devices[max].stepNumber)
|
||||
minStep = int32(d.devices[min].stepNumber)
|
||||
for s := int32(d.devices[max].stepNumber); s < stepsA; s++ {
|
||||
time.Sleep(time.Duration(d.devices[0].stepDelay) * time.Microsecond)
|
||||
time.Sleep(d.devices[0].stepDelay)
|
||||
d.devices[max].moveDirectionSteps(directions[max], s)
|
||||
|
||||
if ((s * stepsB) / stepsA) > minStep {
|
||||
@@ -119,6 +173,18 @@ func (d *DualDevice) Off() {
|
||||
|
||||
// stepMotor changes the pins' state to the correct step
|
||||
func (d *Device) stepMotor(step uint8) {
|
||||
switch d.stepMode {
|
||||
default:
|
||||
fallthrough
|
||||
case ModeFour:
|
||||
d.stepMotor4(step)
|
||||
case ModeEight:
|
||||
d.stepMotor8(step)
|
||||
}
|
||||
}
|
||||
|
||||
// stepMotor4 changes the pins' state to the correct step in 4-step mode
|
||||
func (d *Device) stepMotor4(step uint8) {
|
||||
switch step {
|
||||
case 0:
|
||||
d.pins[0].High()
|
||||
@@ -148,13 +214,63 @@ func (d *Device) stepMotor(step uint8) {
|
||||
d.stepNumber = step
|
||||
}
|
||||
|
||||
// stepMotor8 changes the pins' state to the correct step in 8-step mode
|
||||
func (d *Device) stepMotor8(step uint8) {
|
||||
switch step {
|
||||
case 0:
|
||||
d.pins[0].High()
|
||||
d.pins[2].Low()
|
||||
d.pins[1].Low()
|
||||
d.pins[3].Low()
|
||||
case 1:
|
||||
d.pins[0].High()
|
||||
d.pins[2].High()
|
||||
d.pins[1].Low()
|
||||
d.pins[3].Low()
|
||||
case 2:
|
||||
d.pins[0].Low()
|
||||
d.pins[2].High()
|
||||
d.pins[1].Low()
|
||||
d.pins[3].Low()
|
||||
case 3:
|
||||
d.pins[0].Low()
|
||||
d.pins[2].High()
|
||||
d.pins[1].High()
|
||||
d.pins[3].Low()
|
||||
case 4:
|
||||
d.pins[0].Low()
|
||||
d.pins[2].Low()
|
||||
d.pins[1].High()
|
||||
d.pins[3].Low()
|
||||
case 5:
|
||||
d.pins[0].Low()
|
||||
d.pins[2].Low()
|
||||
d.pins[1].High()
|
||||
d.pins[3].High()
|
||||
case 6:
|
||||
d.pins[0].Low()
|
||||
d.pins[2].Low()
|
||||
d.pins[1].Low()
|
||||
d.pins[3].High()
|
||||
case 7:
|
||||
d.pins[0].High()
|
||||
d.pins[2].Low()
|
||||
d.pins[1].Low()
|
||||
d.pins[3].High()
|
||||
}
|
||||
d.stepNumber = step
|
||||
}
|
||||
|
||||
// moveDirectionSteps uses the direction to calculate the correct step and change the motor to it.
|
||||
// Direction true: 0, 1, 2, 3, 0, 1, 2, ...
|
||||
// Direction false: 0, 3, 2, 1, 0, 3, 2, ...
|
||||
// Direction true: (4-step mode) 0, 1, 2, 3, 0, 1, 2, ...
|
||||
// Direction false: (4-step mode) 0, 3, 2, 1, 0, 3, 2, ...
|
||||
// Direction true: (8-step mode) 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, ...
|
||||
// Direction false: (8-step mode) 0, 7, 6, 5, 4, 3, 2, 1, 0, 7, 6, ...
|
||||
func (d *Device) moveDirectionSteps(direction bool, step int32) {
|
||||
modulus := int32(d.stepMode.stepCount())
|
||||
if direction {
|
||||
d.stepMotor(uint8(step % 4))
|
||||
d.stepMotor(uint8(step % modulus))
|
||||
} else {
|
||||
d.stepMotor(uint8((step + 2*(step%2)) % 4))
|
||||
d.stepMotor(uint8(((-step % modulus) + modulus) % modulus))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package encoders
|
||||
|
||||
type QuadratureDevice struct {
|
||||
cfg QuadratureConfig
|
||||
impl quadratureImpl
|
||||
}
|
||||
|
||||
type QuadratureConfig struct {
|
||||
Precision int
|
||||
}
|
||||
|
||||
type quadratureImpl interface {
|
||||
configure(cfg QuadratureConfig) error
|
||||
readValue() int
|
||||
writeValue(int)
|
||||
}
|
||||
|
||||
func (enc *QuadratureDevice) Configure(cfg QuadratureConfig) error {
|
||||
if cfg.Precision < 1 {
|
||||
cfg.Precision = 4
|
||||
}
|
||||
enc.cfg = cfg
|
||||
return enc.impl.configure(cfg)
|
||||
}
|
||||
|
||||
// Position returns the stored int value for the encoder
|
||||
func (enc *QuadratureDevice) Position() int {
|
||||
return enc.impl.readValue() / enc.cfg.Precision
|
||||
}
|
||||
|
||||
// SetPosition overwrites the currently stored value with the specified int value
|
||||
func (enc *QuadratureDevice) SetPosition(v int) {
|
||||
enc.impl.writeValue(v * enc.cfg.Precision)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//go:build tinygo && (rp2040 || rp2350 || stm32 || k210 || esp32c3 || nrf || sam || (avr && (atmega328p || atmega328pb)))
|
||||
|
||||
// Implementation based on:
|
||||
// https://gist.github.com/aykevl/3fc1683ed77bb0a9c07559dfe857304a
|
||||
|
||||
// Note: build constraints in this file list targets that define machine.PinToggle.
|
||||
// If this is supported for additional targets in the future, they can be added above.
|
||||
|
||||
package encoders
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"runtime/volatile"
|
||||
)
|
||||
|
||||
var (
|
||||
states = []int8{0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0}
|
||||
)
|
||||
|
||||
// NewQuadratureViaInterrupt returns a rotary encoder device that uses GPIO
|
||||
// interrupts and a lookup table to keep track of quadrature state changes.
|
||||
//
|
||||
// This constructur is only available for TinyGo targets for which machine.PinToggle
|
||||
// is defined as a valid interrupt type.
|
||||
func NewQuadratureViaInterrupt(pinA, pinB machine.Pin) *QuadratureDevice {
|
||||
return &QuadratureDevice{impl: &quadInterruptImpl{pinA: pinA, pinB: pinB, oldAB: 0b00000011}}
|
||||
}
|
||||
|
||||
type quadInterruptImpl struct {
|
||||
pinA machine.Pin
|
||||
pinB machine.Pin
|
||||
|
||||
// precision int
|
||||
|
||||
oldAB int
|
||||
value volatile.Register32
|
||||
}
|
||||
|
||||
func (enc *quadInterruptImpl) configure(cfg QuadratureConfig) error {
|
||||
enc.pinA.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
enc.pinA.SetInterrupt(machine.PinToggle, enc.interrupt)
|
||||
|
||||
enc.pinB.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
enc.pinB.SetInterrupt(machine.PinToggle, enc.interrupt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enc *quadInterruptImpl) interrupt(pin machine.Pin) {
|
||||
aHigh, bHigh := enc.pinA.Get(), enc.pinB.Get()
|
||||
enc.oldAB <<= 2
|
||||
if aHigh {
|
||||
enc.oldAB |= 1 << 1
|
||||
}
|
||||
if bHigh {
|
||||
enc.oldAB |= 1
|
||||
}
|
||||
enc.writeValue(enc.readValue() + int(states[enc.oldAB&0x0f]))
|
||||
}
|
||||
|
||||
// readValue gets the value using volatile operations and returns it as an int
|
||||
func (enc *quadInterruptImpl) readValue() int {
|
||||
return int(enc.value.Get())
|
||||
}
|
||||
|
||||
// writeValue set the value to the specified int using volatile operations
|
||||
func (enc *quadInterruptImpl) writeValue(v int) {
|
||||
enc.value.Set(uint32(v))
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// Package ens160 provides a driver for the ScioSense ENS160 digital gas sensor.
|
||||
//
|
||||
// Datasheet: https://www.sciosense.com/wp-content/uploads/2023/12/ENS160-Datasheet.pdf
|
||||
package ens160
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTimeout = 30 * time.Millisecond
|
||||
shortTimeout = 1 * time.Millisecond
|
||||
)
|
||||
|
||||
// Conversion constants for environment data compensation.
|
||||
const (
|
||||
kelvinOffsetMilli = 273150 // 273.15 K in milli-units
|
||||
tempRawFactor = 64 // As per datasheet for TEMP_IN
|
||||
humRawFactor = 512 // As per datasheet for RH_IN
|
||||
milliFactor = 1000 // For converting from milli-units
|
||||
roundingTerm = milliFactor / 2 // For rounding before integer division
|
||||
)
|
||||
|
||||
// validityStrings provides human-readable descriptions for validity flags.
|
||||
var validityStrings = [...]string{
|
||||
ValidityNormalOperation: "normal operation",
|
||||
ValidityWarmUpPhase: "warm-up phase, wait ~3 minutes for valid data",
|
||||
ValidityInitialStartUpPhase: "initial start-up phase, wait ~1 hour for valid data",
|
||||
ValidityInvalidOutput: "invalid output",
|
||||
}
|
||||
|
||||
// Device wraps an I2C connection to an ENS160 device.
|
||||
type Device struct {
|
||||
bus drivers.I2C // I²C implementation
|
||||
addr uint16 // 7‑bit bus address, promoted to uint16 per drivers.I2C
|
||||
|
||||
// shadow registers / last measurements
|
||||
lastTvocPPB uint16
|
||||
lastEco2PPM uint16
|
||||
lastAqiUBA uint8
|
||||
lastValidity uint8 // Store the latest validity status
|
||||
|
||||
// pre‑allocated buffers
|
||||
wbuf [5]byte // longest write: reg + 4 bytes (TEMP+RH)
|
||||
rbuf [5]byte // longest read: DATA burst (5 bytes)
|
||||
}
|
||||
|
||||
// New returns a new ENS160 driver.
|
||||
func New(bus drivers.I2C, addr uint16) *Device {
|
||||
if addr == 0 {
|
||||
addr = DefaultAddress
|
||||
}
|
||||
return &Device{
|
||||
bus: bus,
|
||||
addr: addr,
|
||||
lastValidity: ValidityInvalidOutput,
|
||||
}
|
||||
}
|
||||
|
||||
// Connected returns whether a ENS160 has been found.
|
||||
func (d *Device) Connected() bool {
|
||||
d.wbuf[0] = regPartID
|
||||
err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:2])
|
||||
return err == nil && d.rbuf[0] == LowPartID && d.rbuf[1] == HighPartID
|
||||
}
|
||||
|
||||
// Configure sets up the device for reading.
|
||||
func (d *Device) Configure() error {
|
||||
// 1. Soft-reset. The device will automatically enter IDLE mode.
|
||||
if err := d.write1(regOpMode, ModeReset); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(defaultTimeout)
|
||||
|
||||
// 2. Clear GPR registers, then go to STANDARD mode.
|
||||
if err := d.write1(regCommand, cmdClrGPR); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(defaultTimeout)
|
||||
|
||||
if err := d.write1(regOpMode, ModeStandard); err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(defaultTimeout)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// calculateTempRaw converts temperature from milli-degrees Celsius to the sensor's raw format.
|
||||
func calculateTempRaw(tempMilliC int32) uint16 {
|
||||
// Clip temperature
|
||||
const (
|
||||
minC = -40 * 1000
|
||||
maxC = 85 * 1000
|
||||
)
|
||||
if tempMilliC < minC {
|
||||
tempMilliC = minC
|
||||
} else if tempMilliC > maxC {
|
||||
tempMilliC = maxC
|
||||
}
|
||||
|
||||
// Integer fixed-point conversion to format required by the sensor.
|
||||
// Formula from datasheet: T_IN = (T_ambient_C + 273.15) * 64
|
||||
return uint16((((tempMilliC + kelvinOffsetMilli) * tempRawFactor) + roundingTerm) / milliFactor)
|
||||
}
|
||||
|
||||
// calculateHumRaw converts relative humidity from milli-percent to the sensor's raw format.
|
||||
func calculateHumRaw(rhMilliPct int32) uint16 {
|
||||
// Clip humidity
|
||||
if rhMilliPct < 0 {
|
||||
rhMilliPct = 0
|
||||
} else if rhMilliPct > 100*1000 {
|
||||
rhMilliPct = 100 * 1000
|
||||
}
|
||||
|
||||
// Integer fixed-point conversion to format required by the sensor.
|
||||
// Formula from datasheet: RH_IN = (RH_ambient_% * 512)
|
||||
return uint16(((rhMilliPct * humRawFactor) + roundingTerm) / milliFactor)
|
||||
}
|
||||
|
||||
// SetEnvDataMilli sets the ambient temperature and humidity for compensation.
|
||||
//
|
||||
// tempMilliC is the temperature in milli-degrees Celsius.
|
||||
// rhMilliPct is the relative humidity in milli-percent.
|
||||
func (d *Device) SetEnvDataMilli(tempMilliC, rhMilliPct int32) error {
|
||||
tempRaw := calculateTempRaw(tempMilliC)
|
||||
humRaw := calculateHumRaw(rhMilliPct)
|
||||
|
||||
d.wbuf[0] = regTempIn // start address (auto‑increment)
|
||||
binary.LittleEndian.PutUint16(d.wbuf[1:3], tempRaw)
|
||||
binary.LittleEndian.PutUint16(d.wbuf[3:5], humRaw)
|
||||
|
||||
return d.bus.Tx(d.addr, d.wbuf[:5], nil)
|
||||
}
|
||||
|
||||
// Update refreshes the concentration measurements.
|
||||
func (d *Device) Update(which drivers.Measurement) error {
|
||||
if which&drivers.Concentration == 0 {
|
||||
return nil // nothing requested
|
||||
}
|
||||
|
||||
const maxTries = 1000
|
||||
var (
|
||||
status uint8
|
||||
validity uint8
|
||||
)
|
||||
var gotData bool
|
||||
|
||||
// Poll DEVICE_STATUS until NEWDAT or timeout
|
||||
for range maxTries {
|
||||
var err error
|
||||
status, err = d.read1(regStatus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status&statusSTATER != 0 {
|
||||
return errors.New("ENS160: error (STATER set)")
|
||||
}
|
||||
validity = (status & statusValidityMask) >> statusValidityShift
|
||||
|
||||
if status&statusNEWDAT != 0 {
|
||||
gotData = true
|
||||
break // Always break when data available
|
||||
}
|
||||
time.Sleep(shortTimeout)
|
||||
}
|
||||
if !gotData {
|
||||
return errors.New("ENS160: timeout waiting for NEWDAT")
|
||||
}
|
||||
|
||||
// Burst-read data regardless of validity state
|
||||
d.wbuf[0] = regAQI
|
||||
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:5]); err != nil {
|
||||
return errors.New("ENS160: burst read failed")
|
||||
}
|
||||
|
||||
d.lastAqiUBA = d.rbuf[0]
|
||||
d.lastTvocPPB = binary.LittleEndian.Uint16(d.rbuf[1:3])
|
||||
d.lastEco2PPM = binary.LittleEndian.Uint16(d.rbuf[3:5])
|
||||
d.lastValidity = validity // Store the validity status
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TVOC returns the last total‑VOC concentration in parts‑per‑billion.
|
||||
func (d *Device) TVOC() uint16 { return d.lastTvocPPB }
|
||||
|
||||
// ECO2 returns the last equivalent CO₂ concentration in parts‑per‑million.
|
||||
func (d *Device) ECO2() uint16 { return d.lastEco2PPM }
|
||||
|
||||
// AQI returns the last Air‑Quality Index according to UBA (1–5).
|
||||
func (d *Device) AQI() uint8 { return d.lastAqiUBA }
|
||||
|
||||
// Validity returns the current operating state of the sensor.
|
||||
func (d *Device) Validity() uint8 {
|
||||
return d.lastValidity
|
||||
}
|
||||
|
||||
// ValidityString returns a human-readable string describing the current validity status.
|
||||
func (d *Device) ValidityString() string {
|
||||
if int(d.lastValidity) < len(validityStrings) {
|
||||
return validityStrings[d.lastValidity]
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// write1 writes a single byte to a register.
|
||||
func (d *Device) write1(reg, val uint8) error {
|
||||
d.wbuf[0] = reg
|
||||
d.wbuf[1] = val
|
||||
return d.bus.Tx(d.addr, d.wbuf[:2], nil)
|
||||
}
|
||||
|
||||
// read1 reads a single byte from a register.
|
||||
func (d *Device) read1(reg uint8) (uint8, error) {
|
||||
d.wbuf[0] = reg
|
||||
if err := d.bus.Tx(d.addr, d.wbuf[:1], d.rbuf[:1]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return d.rbuf[0], nil
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ens160
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalculateTempRaw(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
tempMilliC int32
|
||||
expectedRaw uint16
|
||||
}{
|
||||
{"25°C", 25000, 19082},
|
||||
{"-10.5°C", -10500, 16810},
|
||||
{"Min temp", -40000, 14922},
|
||||
{"Below min", -50000, 14922},
|
||||
{"Max temp", 85000, 22922},
|
||||
{"Above max", 90000, 22922},
|
||||
{"Zero", 0, 17482},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
raw := calculateTempRaw(tc.tempMilliC)
|
||||
if raw != tc.expectedRaw {
|
||||
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateHumRaw(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
rhMilliPct int32
|
||||
expectedRaw uint16
|
||||
}{
|
||||
{"50%", 50000, 25600},
|
||||
{"0%", 0, 0},
|
||||
{"100%", 100000, 51200},
|
||||
{"Below 0%", -10000, 0},
|
||||
{"Above 100%", 110000, 51200},
|
||||
{"33.3%", 33300, 17050},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
raw := calculateHumRaw(tc.rhMilliPct)
|
||||
if raw != tc.expectedRaw {
|
||||
t.Errorf("expected %d, got %d", tc.expectedRaw, raw)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package ens160
|
||||
|
||||
// DefaultAddress is the default I2C address for the ENS160 when the ADDR pin is
|
||||
// connected to high (3.3V). When connected to low (GND), the address is 0x52.
|
||||
const DefaultAddress = 0x53
|
||||
|
||||
// Registers
|
||||
const (
|
||||
regPartID = 0x00
|
||||
regOpMode = 0x10
|
||||
regConfig = 0x11
|
||||
regCommand = 0x12
|
||||
regTempIn = 0x13
|
||||
regRhIn = 0x15
|
||||
regStatus = 0x20
|
||||
regAQI = 0x21
|
||||
regTVOC = 0x22
|
||||
regECO2 = 0x24
|
||||
regDataT = 0x30
|
||||
regDataRH = 0x32
|
||||
regMISR = 0x38
|
||||
regGPRWrite = 0x40
|
||||
regGPRRead = 0x48
|
||||
)
|
||||
|
||||
// Operating modes
|
||||
const (
|
||||
ModeDeepSleep = 0x00
|
||||
ModeIdle = 0x01
|
||||
ModeStandard = 0x02
|
||||
ModeReset = 0xF0
|
||||
)
|
||||
|
||||
// Status register bits
|
||||
const (
|
||||
statusSTATAS = 1 << 7
|
||||
statusSTATER = 1 << 6
|
||||
|
||||
statusValidityMask = 0x0C
|
||||
statusValidityShift = 2
|
||||
|
||||
statusNEWDAT = 1 << 1
|
||||
statusNEWGPR = 1 << 0
|
||||
)
|
||||
|
||||
// Validity flags
|
||||
const (
|
||||
ValidityNormalOperation = 0x00
|
||||
ValidityWarmUpPhase = 0x01 // need ~3 minutes until valid data
|
||||
ValidityInitialStartUpPhase = 0x02 // need ~1 hour until valid data
|
||||
ValidityInvalidOutput = 0x03
|
||||
)
|
||||
|
||||
// Commands
|
||||
const (
|
||||
cmdNOP = 0x00
|
||||
cmdGetAppVer = 0x0E
|
||||
cmdClrGPR = 0xCC
|
||||
)
|
||||
|
||||
// Part IDs
|
||||
const (
|
||||
LowPartID = 0x60
|
||||
HighPartID = 0x01
|
||||
)
|
||||
+294
-33
@@ -16,41 +16,301 @@
|
||||
// AT command set:
|
||||
// https://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf
|
||||
//
|
||||
// 02/2023 sfeldma@gmail.com Heavily modified to use netdev interface
|
||||
|
||||
package espat // import "tinygo.org/x/drivers/espat"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"machine"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/net"
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
"tinygo.org/x/drivers/netlink"
|
||||
)
|
||||
|
||||
// Device wraps UART connection to the ESP8266/ESP32.
|
||||
type Device struct {
|
||||
bus machine.UART
|
||||
type Config struct {
|
||||
// UART config
|
||||
Uart *machine.UART
|
||||
Tx machine.Pin
|
||||
Rx machine.Pin
|
||||
}
|
||||
|
||||
type socket struct {
|
||||
inUse bool
|
||||
protocol int
|
||||
laddr netip.AddrPort
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
cfg *Config
|
||||
uart *machine.UART
|
||||
// command responses that come back from the ESP8266/ESP32
|
||||
response []byte
|
||||
|
||||
// data received from a TCP/UDP connection forwarded by the ESP8266/ESP32
|
||||
socketdata []byte
|
||||
data []byte
|
||||
socket socket
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// ActiveDevice is the currently configured Device in use. There can only be one.
|
||||
var ActiveDevice *Device
|
||||
|
||||
// New returns a new espat driver. Pass in a fully configured UART bus.
|
||||
func New(b machine.UART) *Device {
|
||||
return &Device{bus: b, response: make([]byte, 512), socketdata: make([]byte, 0, 1024)}
|
||||
func NewDevice(cfg *Config) *Device {
|
||||
return &Device{
|
||||
cfg: cfg,
|
||||
response: make([]byte, 1500),
|
||||
data: make([]byte, 0, 1500),
|
||||
}
|
||||
}
|
||||
|
||||
// Configure sets up the device for communication.
|
||||
func (d Device) Configure() {
|
||||
ActiveDevice = &d
|
||||
net.ActiveDevice = ActiveDevice
|
||||
func (d *Device) NetConnect(params *netlink.ConnectParams) error {
|
||||
|
||||
if len(params.Ssid) == 0 {
|
||||
return netlink.ErrMissingSSID
|
||||
}
|
||||
|
||||
d.uart = d.cfg.Uart
|
||||
d.uart.Configure(machine.UARTConfig{TX: d.cfg.Tx, RX: d.cfg.Rx})
|
||||
|
||||
// Connect to ESP8266/ESP32
|
||||
fmt.Printf("Connecting to device...")
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
if d.Connected() {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
if !d.Connected() {
|
||||
fmt.Printf("FAILED\r\n")
|
||||
return netlink.ErrConnectFailed
|
||||
}
|
||||
|
||||
fmt.Printf("CONNECTED\r\n")
|
||||
|
||||
// Connect to Wifi AP
|
||||
fmt.Printf("Connecting to Wifi SSID '%s'...", params.Ssid)
|
||||
|
||||
d.SetWifiMode(WifiModeClient)
|
||||
|
||||
err := d.ConnectToAP(params.Ssid, params.Passphrase, 10 /* secs */)
|
||||
if err != nil {
|
||||
fmt.Printf("FAILED\r\n")
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("CONNECTED\r\n")
|
||||
|
||||
ip, err := d.Addr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("DHCP-assigned IP: %s\r\n", ip)
|
||||
fmt.Printf("\r\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) NetDisconnect() {
|
||||
d.DisconnectFromAP()
|
||||
fmt.Printf("\r\nDisconnected from Wifi\r\n\r\n")
|
||||
}
|
||||
|
||||
func (d *Device) NetNotify(cb func(netlink.Event)) {
|
||||
fmt.Printf("\r\n%s\r\n", netlink.ErrNotSupported)
|
||||
}
|
||||
|
||||
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
|
||||
ip, err := d.GetDNS(name)
|
||||
if err != nil {
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
return netip.ParseAddr(ip)
|
||||
}
|
||||
|
||||
func (d *Device) GetHardwareAddr() (net.HardwareAddr, error) {
|
||||
return net.HardwareAddr{}, netlink.ErrNotSupported
|
||||
}
|
||||
|
||||
func (d *Device) Addr() (netip.Addr, error) {
|
||||
resp, err := d.GetClientIP()
|
||||
if err != nil {
|
||||
return netip.Addr{}, err
|
||||
}
|
||||
prefix := "+CIPSTA:ip:"
|
||||
for _, line := range strings.Split(resp, "\n") {
|
||||
if ok := strings.HasPrefix(line, prefix); ok {
|
||||
ip := line[len(prefix)+1 : len(line)-2]
|
||||
return netip.ParseAddr(ip)
|
||||
}
|
||||
}
|
||||
return netip.Addr{}, fmt.Errorf("Error getting IP address")
|
||||
}
|
||||
|
||||
func (d *Device) Socket(domain int, stype int, protocol int) (int, error) {
|
||||
|
||||
switch domain {
|
||||
case netdev.AF_INET:
|
||||
default:
|
||||
return -1, netdev.ErrFamilyNotSupported
|
||||
}
|
||||
|
||||
switch {
|
||||
case protocol == netdev.IPPROTO_TCP && stype == netdev.SOCK_STREAM:
|
||||
case protocol == netdev.IPPROTO_TLS && stype == netdev.SOCK_STREAM:
|
||||
case protocol == netdev.IPPROTO_UDP && stype == netdev.SOCK_DGRAM:
|
||||
default:
|
||||
return -1, netdev.ErrProtocolNotSupported
|
||||
}
|
||||
|
||||
// Only supporting single connection mode, so only one socket at a time
|
||||
if d.socket.inUse {
|
||||
return -1, netdev.ErrNoMoreSockets
|
||||
}
|
||||
d.socket.inUse = true
|
||||
d.socket.protocol = protocol
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (d *Device) Bind(sockfd int, ip netip.AddrPort) error {
|
||||
d.socket.laddr = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error {
|
||||
var err error
|
||||
var addr = ip.Addr().String()
|
||||
var rport = strconv.Itoa(int(ip.Port()))
|
||||
var lport = strconv.Itoa(int(d.socket.laddr.Port()))
|
||||
|
||||
switch d.socket.protocol {
|
||||
case netdev.IPPROTO_TCP:
|
||||
err = d.ConnectTCPSocket(addr, rport)
|
||||
case netdev.IPPROTO_UDP:
|
||||
err = d.ConnectUDPSocket(addr, rport, lport)
|
||||
case netdev.IPPROTO_TLS:
|
||||
err = d.ConnectSSLSocket(host, rport)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if host == "" {
|
||||
return fmt.Errorf("Connect to %s timed out", ip)
|
||||
} else {
|
||||
return fmt.Errorf("Connect to %s:%d timed out", host, ip.Port())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) Listen(sockfd int, backlog int) error {
|
||||
switch d.socket.protocol {
|
||||
case netdev.IPPROTO_UDP:
|
||||
default:
|
||||
return netdev.ErrProtocolNotSupported
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
|
||||
return -1, netip.AddrPort{}, netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
|
||||
// Check if we've timed out
|
||||
if !deadline.IsZero() {
|
||||
if time.Now().After(deadline) {
|
||||
return -1, netdev.ErrTimeout
|
||||
}
|
||||
}
|
||||
err := d.StartSocketSend(len(buf))
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
n, err := d.Write(buf)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
_, err = d.Response(1000)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (d *Device) Send(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
// Break large bufs into chunks so we don't overrun the hw queue
|
||||
|
||||
chunkSize := 1436
|
||||
for i := 0; i < len(buf); i += chunkSize {
|
||||
end := i + chunkSize
|
||||
if end > len(buf) {
|
||||
end = len(buf)
|
||||
}
|
||||
_, err := d.sendChunk(sockfd, buf[i:end], deadline)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (d *Device) Recv(sockfd int, buf []byte, flags int, deadline time.Time) (int, error) {
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
var length = len(buf)
|
||||
|
||||
// Limit length read size to chunk large read requests
|
||||
if length > 1436 {
|
||||
length = 1436
|
||||
}
|
||||
|
||||
for {
|
||||
// Check if we've timed out
|
||||
if !deadline.IsZero() {
|
||||
if time.Now().After(deadline) {
|
||||
return -1, netdev.ErrTimeout
|
||||
}
|
||||
}
|
||||
|
||||
n, err := d.ReadSocket(buf[:length])
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if n == 0 {
|
||||
d.mu.Unlock()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
d.mu.Lock()
|
||||
continue
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) Close(sockfd int) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.socket.inUse = false
|
||||
return d.DisconnectSocket()
|
||||
}
|
||||
|
||||
func (d *Device) SetSockOpt(sockfd int, level int, opt int, value interface{}) error {
|
||||
return netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
// Connected checks if there is communication with the ESP8266/ESP32.
|
||||
@@ -58,7 +318,7 @@ func (d *Device) Connected() bool {
|
||||
d.Execute(Test)
|
||||
|
||||
// handle response here, should include "OK"
|
||||
_, err := d.Response(100)
|
||||
_, err := d.Response(1000)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -67,12 +327,12 @@ func (d *Device) Connected() bool {
|
||||
|
||||
// Write raw bytes to the UART.
|
||||
func (d *Device) Write(b []byte) (n int, err error) {
|
||||
return d.bus.Write(b)
|
||||
return d.uart.Write(b)
|
||||
}
|
||||
|
||||
// Read raw bytes from the UART.
|
||||
func (d *Device) Read(b []byte) (n int, err error) {
|
||||
return d.bus.Read(b)
|
||||
return d.uart.Read(b)
|
||||
}
|
||||
|
||||
// how long in milliseconds to pause after sending AT commands
|
||||
@@ -101,9 +361,10 @@ func (d Device) Set(cmd, params string) error {
|
||||
// Version returns the ESP8266/ESP32 firmware version info.
|
||||
func (d Device) Version() []byte {
|
||||
d.Execute(Version)
|
||||
r, err := d.Response(100)
|
||||
r, err := d.Response(2000)
|
||||
if err != nil {
|
||||
return []byte("unknown")
|
||||
//return []byte("unknown")
|
||||
return []byte(err.Error())
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -133,16 +394,16 @@ func (d *Device) ReadSocket(b []byte) (n int, err error) {
|
||||
d.Response(300)
|
||||
|
||||
count := len(b)
|
||||
if len(b) >= len(d.socketdata) {
|
||||
if len(b) >= len(d.data) {
|
||||
// copy it all, then clear socket data
|
||||
count = len(d.socketdata)
|
||||
copy(b, d.socketdata[:count])
|
||||
d.socketdata = d.socketdata[:0]
|
||||
count = len(d.data)
|
||||
copy(b, d.data[:count])
|
||||
d.data = d.data[:0]
|
||||
} else {
|
||||
// copy all we can, then keep the remaining socket data around
|
||||
copy(b, d.socketdata[:count])
|
||||
copy(d.socketdata, d.socketdata[count:])
|
||||
d.socketdata = d.socketdata[:len(d.socketdata)-count]
|
||||
copy(b, d.data[:count])
|
||||
copy(d.data, d.data[count:])
|
||||
d.data = d.data[:len(d.data)-count]
|
||||
}
|
||||
|
||||
return count, nil
|
||||
@@ -158,11 +419,11 @@ func (d *Device) Response(timeout int) ([]byte, error) {
|
||||
retries := timeout / pause
|
||||
|
||||
for {
|
||||
size = d.bus.Buffered()
|
||||
size = d.uart.Buffered()
|
||||
|
||||
if size > 0 {
|
||||
end += size
|
||||
d.bus.Read(d.response[start:end])
|
||||
d.uart.Read(d.response[start:end])
|
||||
|
||||
// if "+IPD" then read socket data
|
||||
if strings.Contains(string(d.response[:end]), "+IPD") {
|
||||
@@ -205,18 +466,18 @@ func (d *Device) parseIPD(end int) error {
|
||||
val := string(d.response[s+5 : e])
|
||||
|
||||
// TODO: verify count
|
||||
_, err := strconv.Atoi(val)
|
||||
v, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
// not expected data here. what to do?
|
||||
return err
|
||||
}
|
||||
|
||||
// load up the socket data
|
||||
d.socketdata = append(d.socketdata, d.response[e+1:end]...)
|
||||
d.data = append(d.data, d.response[e+1:e+1+v]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSocketDataAvailable returns of there is socket data available
|
||||
func (d *Device) IsSocketDataAvailable() bool {
|
||||
return len(d.socketdata) > 0 || d.bus.Buffered() > 0
|
||||
return len(d.data) > 0 || d.uart.Buffered() > 0
|
||||
}
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ func (d *Device) GetDNS(domain string) (string, error) {
|
||||
return "", errors.New("Invalid domain lookup result")
|
||||
}
|
||||
res := strings.Split(r[1], "\r\n")
|
||||
return res[0], nil
|
||||
return strings.Trim(res[0], `"`), nil
|
||||
}
|
||||
|
||||
// ConnectTCPSocket creates a new TCP socket connection for the ESP8266/ESP32.
|
||||
@@ -51,7 +51,7 @@ func (d *Device) ConnectTCPSocket(addr, port string) error {
|
||||
// ConnectUDPSocket creates a new UDP connection for the ESP8266/ESP32.
|
||||
func (d *Device) ConnectUDPSocket(addr, sendport, listenport string) error {
|
||||
protocol := "UDP"
|
||||
val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",2"
|
||||
val := "\"" + protocol + "\",\"" + addr + "\"," + sendport + "," + listenport + ",0"
|
||||
err := d.Set(TCPConnect, val)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+1
-4
@@ -44,10 +44,7 @@ func (d *Device) ConnectToAP(ssid, pwd string, ws int) error {
|
||||
d.Set(ConnectAP, val)
|
||||
|
||||
_, err := d.Response(ws * 1000)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// DisconnectFromAP disconnects the ESP8266/ESP32 from the current access point.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"machine"
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/adafruit4650"
|
||||
"tinygo.org/x/tinyfont"
|
||||
"tinygo.org/x/tinyfont/freemono"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
dev := adafruit4650.New(machine.I2C0)
|
||||
|
||||
err := dev.Configure()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
drawPlus(&dev)
|
||||
drawHelloWorld(&dev)
|
||||
|
||||
err = dev.Display()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func drawPlus(d drivers.Displayer) {
|
||||
for i := int16(0); i < 128; i++ {
|
||||
d.SetPixel(i, 32, color.RGBA{R: 1})
|
||||
}
|
||||
for i := int16(0); i < 64; i++ {
|
||||
d.SetPixel(64, i, color.RGBA{R: 1})
|
||||
}
|
||||
}
|
||||
|
||||
func drawHelloWorld(d drivers.Displayer) {
|
||||
tinyfont.WriteLine(d, &freemono.Regular9pt7b, 0, 32, "Hello World!", color.RGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff})
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
i2c = &machine.I2C0
|
||||
i2c = machine.I2C0
|
||||
sensor = adt7410.New(i2c)
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/aht20"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
dev := aht20.New(machine.I2C0)
|
||||
dev.Configure()
|
||||
|
||||
dev.Reset()
|
||||
for {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
err := dev.Read()
|
||||
if err != nil {
|
||||
println("Error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
println("temp ", fmtD(dev.DeciCelsius(), 3, 1), "C")
|
||||
println("humidity", fmtD(dev.DeciRelHumidity(), 3, 1), "%")
|
||||
}
|
||||
}
|
||||
|
||||
func fmtD(val int32, i int, f int) string {
|
||||
result := make([]byte, i+f+1)
|
||||
neg := false
|
||||
|
||||
if val < 0 {
|
||||
val = -val
|
||||
neg = true
|
||||
}
|
||||
|
||||
for p := len(result) - 1; p >= 0; p-- {
|
||||
result[p] = byte(int32('0') + (val % 10))
|
||||
val = val / 10
|
||||
|
||||
if p == i+1 && p > 0 {
|
||||
p--
|
||||
result[p] = '.'
|
||||
}
|
||||
}
|
||||
|
||||
if neg {
|
||||
result[0] = '-'
|
||||
}
|
||||
|
||||
return string(result)
|
||||
}
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
apa apa102.Device
|
||||
apa *apa102.Device
|
||||
|
||||
led = machine.PWM{machine.LED}
|
||||
pwm = machine.TCC0
|
||||
leds = make([]color.RGBA, 1)
|
||||
wheel = &Wheel{Brightness: 0x10}
|
||||
)
|
||||
@@ -27,12 +27,19 @@ func init() {
|
||||
apa = apa102.NewSoftwareSPI(machine.PA00, machine.PA01, 1)
|
||||
|
||||
// Configure the regular on-board LED for PWM fading
|
||||
machine.InitPWM()
|
||||
led.Configure()
|
||||
|
||||
err := pwm.Configure(machine.PWMConfig{})
|
||||
if err != nil {
|
||||
println("failed to configure PWM")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
channelLED, err := pwm.Channel(machine.LED)
|
||||
if err != nil {
|
||||
println("failed to configure LED PWM channel")
|
||||
return
|
||||
}
|
||||
|
||||
// We'll fade the on-board LED in a goroutine to show/ensure that the APA102
|
||||
// works fine with the scheduler enabled. Comment this out to test this code
|
||||
@@ -43,11 +50,11 @@ func main() {
|
||||
brightening = !brightening
|
||||
continue
|
||||
}
|
||||
var brightness uint16 = uint16(i) << 8
|
||||
var brightness uint32 = uint32(i)
|
||||
if !brightening {
|
||||
brightness = 0xFFFF - brightness
|
||||
brightness = 256 - brightness
|
||||
}
|
||||
led.Set(brightness)
|
||||
pwm.Set(channelLED, pwm.Top()*brightness/256)
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/apds9960"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// use Nano 33 BLE Sense's internal I2C bus
|
||||
machine.I2C1.Configure(machine.I2CConfig{
|
||||
SCL: machine.SCL1_PIN,
|
||||
SDA: machine.SDA1_PIN,
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
|
||||
sensor := apds9960.New(machine.I2C1)
|
||||
|
||||
sensor.Configure(apds9960.Configuration{}) // use default settings
|
||||
|
||||
if !sensor.Connected() {
|
||||
println("APDS-9960 not connected!")
|
||||
return
|
||||
}
|
||||
|
||||
sensor.EnableColor() // enable color engine
|
||||
|
||||
for {
|
||||
|
||||
if sensor.ColorAvailable() {
|
||||
r, g, b, c := sensor.ReadColor()
|
||||
println("Red =", r, "\tGreen =", g, "\tBlue =", b, "\tClear =", c)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/apds9960"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// use Nano 33 BLE Sense's internal I2C bus
|
||||
machine.I2C1.Configure(machine.I2CConfig{
|
||||
SCL: machine.SCL1_PIN,
|
||||
SDA: machine.SDA1_PIN,
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
|
||||
sensor := apds9960.New(machine.I2C1)
|
||||
|
||||
sensor.Configure(apds9960.Configuration{}) // use default settings
|
||||
|
||||
if !sensor.Connected() {
|
||||
println("APDS-9960 not connected!")
|
||||
return
|
||||
}
|
||||
|
||||
sensor.EnableGesture() // enable gesture engine
|
||||
|
||||
for {
|
||||
|
||||
// wave your hand (not too slow) about 10 cm above the sensor
|
||||
if sensor.GestureAvailable() {
|
||||
|
||||
gesture := sensor.ReadGesture()
|
||||
print("Detected gesture: ")
|
||||
switch gesture {
|
||||
case apds9960.GESTURE_UP: // the nRF52 chip is "up"
|
||||
println("Up")
|
||||
case apds9960.GESTURE_DOWN:
|
||||
println("Down")
|
||||
case apds9960.GESTURE_LEFT:
|
||||
println("Left")
|
||||
case apds9960.GESTURE_RIGHT:
|
||||
println("Right")
|
||||
}
|
||||
}
|
||||
// note: the delay shouldn't be too long, otherwise new gesture data might be lost
|
||||
time.Sleep(time.Millisecond * 250)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/apds9960"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
// use Nano 33 BLE Sense's internal I2C bus
|
||||
machine.I2C1.Configure(machine.I2CConfig{
|
||||
SCL: machine.SCL1_PIN,
|
||||
SDA: machine.SDA1_PIN,
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
})
|
||||
|
||||
sensor := apds9960.New(machine.I2C1)
|
||||
|
||||
// use default settings
|
||||
sensor.Configure(apds9960.Configuration{})
|
||||
|
||||
if !sensor.Connected() {
|
||||
println("APDS-9960 not connected!")
|
||||
return
|
||||
}
|
||||
|
||||
sensor.EnableProximity() // enable proximity engine
|
||||
|
||||
for {
|
||||
|
||||
if sensor.ProximityAvailable() {
|
||||
p := sensor.ReadProximity()
|
||||
println("Proximity:", p)
|
||||
}
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"machine/usb/hid/mouse"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/as560x"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Let's use the AS5600 to make the world's most useless mouse with just a single X-axis & no buttons (!)
|
||||
machine.I2C0.Configure(machine.I2CConfig{
|
||||
Frequency: machine.TWI_FREQ_400KHZ,
|
||||
SDA: machine.GPIO4,
|
||||
SCL: machine.GPIO5,
|
||||
})
|
||||
as5600 := as560x.NewAS5600(machine.I2C0)
|
||||
as5600.Configure(as560x.Config{})
|
||||
mouse := mouse.New()
|
||||
|
||||
lastAngle := -1
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
// Get the magnet status of the AS5600
|
||||
magnetDetected, magnetStrength, err := as5600.MagnetStatus()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// Get the raw angle from the AS5600
|
||||
angle, _, err := as5600.RawAngle(as560x.ANGLE_NATIVE)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
str := ""
|
||||
if !magnetDetected {
|
||||
str += "NOT "
|
||||
}
|
||||
str += "detected. Strength is "
|
||||
switch magnetStrength {
|
||||
case as560x.MagnetTooWeak:
|
||||
str += "too weak"
|
||||
case as560x.MagnetTooStrong:
|
||||
str += "too strong"
|
||||
default:
|
||||
str += "ok"
|
||||
}
|
||||
println("Raw angle:", angle, "Magnet was", str)
|
||||
if lastAngle != -1 {
|
||||
diff := int(angle) - lastAngle
|
||||
// correct the zero crossover glitch
|
||||
if diff < -0xc00 {
|
||||
diff += 0xfff
|
||||
} else if diff > 0xc00 {
|
||||
diff -= 0xfff
|
||||
}
|
||||
// debounce the noise (could use the sensor's filters/hysteresis instead?)
|
||||
if math.Abs(float64(diff)) > 2 {
|
||||
// move the mouse x-axis in response to the AS5600
|
||||
mouse.Move(diff, 0)
|
||||
}
|
||||
}
|
||||
lastAngle = int(angle)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
axp192 "tinygo.org/x/drivers/axp192/m5stack-core2-axp192"
|
||||
"tinygo.org/x/drivers/i2csoft"
|
||||
)
|
||||
|
||||
func main() {
|
||||
i2c := i2csoft.New(machine.SCL0_PIN, machine.SDA0_PIN)
|
||||
i2c.Configure(i2csoft.I2CConfig{Frequency: 100e3})
|
||||
|
||||
axp := axp192.New(i2c)
|
||||
led := axp.LED
|
||||
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
|
||||
led.High()
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
// Smoke test for the BMA421/BMA425 sensors.
|
||||
// Warning: this code has _not been tested_. It's only here as a smoke test.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"machine"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/bma42x"
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
i2cBus := machine.I2C1
|
||||
i2cBus.Configure(machine.I2CConfig{
|
||||
Frequency: 400 * machine.KHz,
|
||||
SDA: machine.SDA_PIN,
|
||||
SCL: machine.SCL_PIN,
|
||||
})
|
||||
|
||||
sensor := bma42x.NewI2C(i2cBus, bma42x.Address)
|
||||
err := sensor.Configure(bma42x.Config{
|
||||
Device: bma42x.DeviceBMA421 | bma42x.DeviceBMA425,
|
||||
Features: bma42x.FeatureStepCounting,
|
||||
})
|
||||
if err != nil {
|
||||
println("could not configure BMA421/BMA425:", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !sensor.Connected() {
|
||||
println("BMA42x not connected")
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
err := sensor.Update(drivers.Acceleration | drivers.Temperature)
|
||||
if err != nil {
|
||||
println("Error reading sensor", err)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("Temperature: %.2f °C\n", float32(sensor.Temperature())/1000)
|
||||
|
||||
accelX, accelY, accelZ := sensor.Acceleration()
|
||||
fmt.Printf("Acceleration: %.2fg %.2fg %.2fg\n", float32(accelX)/1e6, float32(accelY)/1e6, float32(accelZ)/1e6)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ func main() {
|
||||
pressure, _ := sensor.ReadPressure()
|
||||
println("Pressure", float32(pressure)/100000, "hPa")
|
||||
|
||||
altitude, _ := sensor.ReadAltitude()
|
||||
println("Altitude", altitude, "meters")
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user