mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-05 15:33:42 +00:00
Compare commits
99 Commits
http-chunked
...
v0.25.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -11,10 +11,13 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: tinygo/tinygo-dev
|
container: ghcr.io/tinygo-org/tinygo-dev
|
||||||
steps:
|
steps:
|
||||||
|
- name: Work around CVE-2022-24765
|
||||||
|
# We're not on a multi-user machine, so this is safe.
|
||||||
|
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: TinyGo version check
|
- name: TinyGo version check
|
||||||
run: tinygo version
|
run: tinygo version
|
||||||
- name: Enforce Go Formatted Code
|
- name: Enforce Go Formatted Code
|
||||||
|
|||||||
+156
@@ -1,3 +1,159 @@
|
|||||||
|
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
|
0.23.0
|
||||||
---
|
---
|
||||||
- **new devices**
|
- **new devices**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved.
|
Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
|||||||
@@ -7,248 +7,11 @@ FMT_PATHS = ./
|
|||||||
fmt-check:
|
fmt-check:
|
||||||
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
@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:
|
smoke-test:
|
||||||
@mkdir -p build
|
@mkdir -p build
|
||||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
|
@go run ./smoketest.go -xtensa=$(XTENSA) smoketest.sh
|
||||||
@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=nano-33-ble ./examples/apds9960/proximity/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=trinket-m0 ./examples/bmp388/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=microbit ./examples/gc9a01/main.go
|
|
||||||
@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=nano-33-ble ./examples/hts221/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=pyportal ./examples/ili9341/slideshow
|
|
||||||
@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=nano-33-ble ./examples/lps22hb/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/mcp23017/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/mcp23017-multiple/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=itsybitsy-m0 ./examples/mcp2515/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=microbit-v2 ./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=p1am-100 ./examples/p1am/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/pca9685/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=arduino ./examples/servo
|
|
||||||
@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/shtc3/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=circuitplay-bluefruit ./examples/tone
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/tm1637/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=itsybitsy-m0 ./examples/vl6180x/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
|
|
||||||
tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examples/ws2812
|
|
||||||
@md5sum ./build/test.bin
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
ifneq ($(AVR), 0)
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
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
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/max72xx/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/dht/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
# tinygo build -size short -o ./build/test.hex -target=arduino ./examples/keypad4x4/main.go
|
|
||||||
# @md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/alarm/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/clkout/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/time/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=xiao ./examples/pcf8563/timer/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/qmi8658c/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/ina260/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=nucleo-l432kc ./examples/aht20/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/console/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/sdcard/tinyfs/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/webclient/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/webserver/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=wioterminal ./examples/rtl8720dn/mqttsub/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=feather-m4 ./examples/i2csoft/adt7410/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.elf -target=wioterminal ./examples/axp192/m5stack-core2-blinky/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/xpt2046/main.go
|
|
||||||
@md5sum ./build/test.uf2
|
|
||||||
tinygo build -size short -o ./build/test.elf -target=m5stack-core2 ./examples/ft6336/basic/
|
|
||||||
@md5sum ./build/test.elf
|
|
||||||
tinygo build -size short -o ./build/test.elf -target=m5stack-core2 ./examples/ft6336/touchpaint/
|
|
||||||
@md5sum ./build/test.elf
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=nucleo-wl55jc ./examples/sx126x/lora_rxtx/
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/ssd1289/main.go
|
|
||||||
@md5sum ./build/test.uf2
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/irremote/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.hex -target=badger2040 ./examples/uc8151/main.go
|
|
||||||
@md5sum ./build/test.hex
|
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=pico ./examples/scd4x/main.go
|
|
||||||
@md5sum ./build/test.uf2
|
|
||||||
tinygo build -size short -o ./build/test.uf2 -target=circuitplay-express ./examples/makeybutton/main.go
|
|
||||||
@md5sum ./build/test.uf2
|
|
||||||
|
|
||||||
# rwildcard is a recursive version of $(wildcard)
|
# rwildcard is a recursive version of $(wildcard)
|
||||||
# https://blog.jgc.org/2011/07/gnu-make-recursive-wildcard-function.html
|
# https://blog.jgc.org/2011/07/gnu-make-recursive-wildcard-function.html
|
||||||
|
|||||||
@@ -50,102 +50,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Currently supported devices
|
## Supported devices
|
||||||
|
|
||||||
The following 90 devices are supported.
|
There are currently 96 devices supported. For the complete list, please see:
|
||||||
|
https://tinygo.org/docs/reference/devices/
|
||||||
| 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 |
|
|
||||||
| [AHT20 I2C Temperature and Humidity Sensor](http://www.aosong.com/userfiles/files/media/AHT20%20%E8%8B%B1%E6%96%87%E7%89%88%E8%AF%B4%E6%98%8E%E4%B9%A6%20A0%2020201222.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 |
|
|
||||||
| [APDS9960 Digital proximity, ambient light, RGB and gesture sensor](https://cdn.sparkfun.com/assets/learn_tutorials/3/2/1/Avago-APDS-9960-datasheet.pdf) | I2C |
|
|
||||||
| [AT24CX 2-wire serial EEPROM](https://www.openimpulse.com/blog/wp-content/uploads/wpsc/downloadables/24C32-Datasheet.pdf) | I2C |
|
|
||||||
| [AXP192 single Cell Li-Battery and Power System Management](https://github.com/m5stack/M5-Schematic/blob/master/Core/AXP192%20Datasheet_v1.1_en_draft_2211.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 |
|
|
||||||
| [BMP388 pressure sensor](https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf) | I2C |
|
|
||||||
| [Buzzer](https://en.wikipedia.org/wiki/Buzzer#Piezoelectric) | GPIO |
|
|
||||||
| [DHTXX thermometer and humidity sensor](https://cdn-shop.adafruit.com/datasheets/Digital+humidity+and+temperature+sensor+AM2302.pdf) | 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 |
|
|
||||||
| [FT6336 touch controller](https://focuslcds.com/content/FT6236.pdf) | I2C |
|
|
||||||
| [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 |
|
|
||||||
| [HTS221 digital humidity and temperature sensor](https://www.st.com/resource/en/datasheet/hts221.pdf) | I2C |
|
|
||||||
| [HUB75 RGB led matrix](https://cdn-learn.adafruit.com/downloads/pdf/32x16-32x32-rgb-led-matrix.pdf) | SPI |
|
|
||||||
| [software I2C driver](https://www.ti.com/lit/an/slva704/slva704.pdf) | GPIO |
|
|
||||||
| [ILI9341 TFT color display](https://cdn-shop.adafruit.com/datasheets/ILI9341.pdf) | SPI |
|
|
||||||
| [INA260 Volt/Amp/Power meter](https://www.ti.com/lit/ds/symlink/ina260.pdf) | I2C |
|
|
||||||
| [Infrared remote control](https://en.wikipedia.org/wiki/Consumer_IR) | GPIO |
|
|
||||||
| [IS31FL3731 matrix LED driver](https://www.lumissil.com/assets/pdf/core/IS31FL3731_DS.pdf) | I2C |
|
|
||||||
| [4x4 Membrane Keypad](https://cdn.sparkfun.com/assets/f/f/a/5/0/DS-16038.pdf) | GPIO |
|
|
||||||
| [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 |
|
|
||||||
| [LPS22HB MEMS nano pressure sensor](https://www.st.com/resource/en/datasheet/dm00140895.pdf) | I2C |
|
|
||||||
| [LSM6DS3 accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3.pdf) | I2C |
|
|
||||||
| [LSM6DSOX accelerometer](https://www.st.com/resource/en/datasheet/lsm6dsox.pdf) | I2C |
|
|
||||||
| [LSM6DS3TR accelerometer](https://www.st.com/resource/en/datasheet/lsm6ds3tr.pdf) | I2C |
|
|
||||||
| [LSM303AGR accelerometer](https://www.st.com/resource/en/datasheet/lsm303agr.pdf) | I2C |
|
|
||||||
| [LSM9DS1 accelerometer](https://www.st.com/resource/en/datasheet/lsm9ds1.pdf) | I2C |
|
|
||||||
| [Makey Button](https://makeymakey.com/) | GPIO |
|
|
||||||
| [MAG3110 magnetometer](https://www.nxp.com/docs/en/data-sheet/MAG3110.pdf) | I2C |
|
|
||||||
| [MAX7219 & MAX7221 display driver](https://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf) | SPI |
|
|
||||||
| [MCP2515 Stand-Alone CAN Controller with SPI Interface](https://ww1.microchip.com/downloads/en/DeviceDoc/MCP2515-Family-Data-Sheet-DS20001801K.pdf) | SPI |
|
|
||||||
| [MCP3008 analog to digital converter (ADC)](http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf) | SPI |
|
|
||||||
| [MCP23017 port expander](https://ww1.microchip.com/downloads/en/DeviceDoc/20001952C.pdf) | I2C |
|
|
||||||
| [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 |
|
|
||||||
| [P1AM-100 Base Controller](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html) | SPI |
|
|
||||||
| [PCD8544 display](http://eia.udg.edu/~forest/PCD8544_1.pdf) | SPI |
|
|
||||||
| [PCF8563 real time clock](https://www.nxp.com/docs/en/data-sheet/PCF8563.pdf) | I2C |
|
|
||||||
| [QMI8658C accelerometer/gyroscope](https://www.qstcorp.com/upload/pdf/202202/%EF%BC%88%E5%B7%B2%E4%BC%A0%EF%BC%89QMI8658C%20datasheet%20rev%200.9.pdf) | I2C |
|
|
||||||
| [Resistive Touchscreen (4-wire)](http://ww1.microchip.com/downloads/en/Appnotes/doc8091.pdf) | GPIO |
|
|
||||||
| [RTL8720DN 2.4G/5G Dual Bands Wireless and BLE5.0](https://www.seeedstudio.com/Realtek8720DN-2-4G-5G-Dual-Bands-Wireless-and-BLE5-0-Combo-Module-p-4442.html) | UART |
|
|
||||||
| [SCD4x CO2 Sensor](https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf) | I2C |
|
|
||||||
| [Semihosting](https://wiki.segger.com/Semihosting) | Debug |
|
|
||||||
| [Servo](https://learn.sparkfun.com/tutorials/hobby-servo-tutorial/all) | PWM |
|
|
||||||
| [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/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C |
|
|
||||||
| [SHTC3 Digital Humidity Sensor (RH/T)](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Datasheets/Sensirion_Humidity_Sensors_SHTC3_Datasheet.pdf) | I2C |
|
|
||||||
| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI |
|
|
||||||
| [SPI SDCARD/MMC](https://en.wikipedia.org/wiki/SD_card) | SPI |
|
|
||||||
| [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 |
|
|
||||||
| [TM1637 7-segment LED display](https://www.mcielectronics.cl/website_MCI/static/documents/Datasheet_TM1637.pdf) | I2C |
|
|
||||||
| [TMP102 I2C Temperature Sensor](https://download.mikroe.com/documents/datasheets/tmp102-data-sheet.pdf) | I2C |
|
|
||||||
| [UC8151 All-in-one driver IC for ESL](https://www.buydisplay.com/download/ic/UC8151C.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 |
|
|
||||||
| [VL6180X time-of-flight distance sensor](https://www.st.com/resource/en/datasheet/vl6180x.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 2.9" e-paper display (V1)](https://www.waveshare.com/w/upload/e/e6/2.9inch_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 |
|
|
||||||
| [Waveshare GC9A01 TFT round display](https://www.waveshare.com/w/upload/5/5e/GC9A01A.pdf) | SPI |
|
|
||||||
| [WS2812 RGB LED](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) | GPIO |
|
|
||||||
| [XPT2046 touch controller](http://grobotronics.com/images/datasheets/xpt2046-datasheet.pdf) | GPIO |
|
|
||||||
| [Semtech SX126x Lora](https://www.semtech.com/products/wireless-rf/lora-connect/sx1261) | SPI |
|
|
||||||
| [SSD1289 TFT color display](http://aitendo3.sakura.ne.jp/aitendo_data/product_img/lcd/tft2/M032C1289TP/3.2-SSD1289.pdf) | GPIO |
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -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,169 @@
|
|||||||
|
package as560x // import tinygo.org/x/drivers/ams560x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 := bus.ReadRegister(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 := bus.WriteRegister(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
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
package bmp180 // import "tinygo.org/x/drivers/bmp180"
|
package bmp180 // import "tinygo.org/x/drivers/bmp180"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tinygo.org/x/drivers"
|
"tinygo.org/x/drivers"
|
||||||
@@ -123,6 +124,21 @@ func (d *Device) ReadPressure() (pressure int32, err error) {
|
|||||||
return 1000 * (p + ((x1 + x2 + 3791) >> 4)), nil
|
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
|
// rawTemp returns the sensor's raw values of the temperature
|
||||||
func (d *Device) rawTemp() (int32, error) {
|
func (d *Device) rawTemp() (int32, error) {
|
||||||
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
|
d.bus.WriteRegister(uint8(d.Address), REG_CTRL, []byte{CMD_TEMP})
|
||||||
|
|||||||
@@ -28,3 +28,7 @@ const (
|
|||||||
// ULTRAHIGHRESOLUTION is the highest oversampling mode of the pressure measurement.
|
// ULTRAHIGHRESOLUTION is the highest oversampling mode of the pressure measurement.
|
||||||
ULTRAHIGHRESOLUTION
|
ULTRAHIGHRESOLUTION
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SEALEVEL_PRESSURE float32 = 1013.25 // in hPa
|
||||||
|
)
|
||||||
|
|||||||
@@ -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(cycles)
|
||||||
|
if !slept {
|
||||||
|
// Fallback for platforms without inline assembly support.
|
||||||
|
time.Sleep(duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,3 +12,19 @@ type Displayer interface {
|
|||||||
// Display sends the buffer (if any) to the screen.
|
// Display sends the buffer (if any) to the screen.
|
||||||
Display() error
|
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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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, int) 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, 8) != spb[8] {
|
||||||
|
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
|
||||||
|
}
|
||||||
+36
-2
@@ -73,7 +73,17 @@ func (d *Device) SetRunning(isRunning bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
func (d *Device) SetTime(dt time.Time) error {
|
||||||
data := []byte{0}
|
data := []byte{0}
|
||||||
err := d.bus.ReadRegister(uint8(d.Address), REG_STATUS, data)
|
err := d.bus.ReadRegister(uint8(d.Address), REG_STATUS, data)
|
||||||
@@ -92,6 +102,10 @@ func (d *Device) SetTime(dt time.Time) error {
|
|||||||
data[2] = uint8ToBCD(uint8(dt.Hour()))
|
data[2] = uint8ToBCD(uint8(dt.Hour()))
|
||||||
|
|
||||||
year := uint8(dt.Year() - 2000)
|
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)
|
centuryFlag := uint8(0)
|
||||||
if year >= 100 {
|
if year >= 100 {
|
||||||
year -= 100
|
year -= 100
|
||||||
@@ -140,7 +154,27 @@ func (d *Device) ReadTemperature() (int32, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return int32(data[0])*1000 + int32((data[1]>>6)*25)*10, nil
|
return milliCelsius(data[0], data[1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(msb uint8, lsb uint8) int32 {
|
||||||
|
t256 := int16(uint16(msb)<<8 | uint16(lsb))
|
||||||
|
t1000 := int32(t256) / 64 * 250
|
||||||
|
return t1000
|
||||||
}
|
}
|
||||||
|
|
||||||
// uint8ToBCD converts a byte to BCD for the DS3231
|
// uint8ToBCD converts a byte to BCD for the DS3231
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package ds3231
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPositiveMilliCelsius(t *testing.T) {
|
||||||
|
t1000 := milliCelsius(0, 0)
|
||||||
|
if t1000 != 0 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(0, 0b01000000)
|
||||||
|
if t1000 != 250 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(0, 0b10000000)
|
||||||
|
if t1000 != 500 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(0, 0b11000000)
|
||||||
|
if t1000 != 750 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(1, 0b00000000)
|
||||||
|
if t1000 != 1000 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(2, 0b00000000)
|
||||||
|
if t1000 != 2000 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// highest temperature is 127.750C
|
||||||
|
t1000 = milliCelsius(0x7f, 0b11000000)
|
||||||
|
if t1000 != 127750 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNegativeMilliCelsius(t *testing.T) {
|
||||||
|
t1000 := milliCelsius(0xff, 0b11000000)
|
||||||
|
if t1000 != -250 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(0xff, 0b10000000)
|
||||||
|
if t1000 != -500 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(0xff, 0b01000000)
|
||||||
|
if t1000 != -750 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(0xff, 0b00000000)
|
||||||
|
if t1000 != -1000 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
t1000 = milliCelsius(0xfe, 0b00000000)
|
||||||
|
if t1000 != -2000 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// lowest temperature is -128.000C
|
||||||
|
t1000 = milliCelsius(0x80, 0b00000000)
|
||||||
|
if t1000 != -128000 {
|
||||||
|
t.Fatal(t1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,9 @@ func main() {
|
|||||||
pressure, _ := sensor.ReadPressure()
|
pressure, _ := sensor.ReadPressure()
|
||||||
println("Pressure", float32(pressure)/100000, "hPa")
|
println("Pressure", float32(pressure)/100000, "hPa")
|
||||||
|
|
||||||
|
altitude, _ := sensor.ReadAltitude()
|
||||||
|
println("Altitude", altitude, "meters")
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/delay"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Sleep(time.Second) // wait for a serial console
|
||||||
|
start := time.Now()
|
||||||
|
for i := 0; i < 2000; i++ {
|
||||||
|
delay.Sleep(50 * time.Microsecond)
|
||||||
|
}
|
||||||
|
duration := time.Since(start)
|
||||||
|
println("sleep of 2000*50µs (100ms) took:", duration.String())
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/onewire"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/ds18b20"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Define pin for DS18B20
|
||||||
|
pin := machine.D2
|
||||||
|
|
||||||
|
ow := onewire.New(pin)
|
||||||
|
romIDs, err := ow.Search(onewire.SEARCH_ROM)
|
||||||
|
if err != nil {
|
||||||
|
println(err)
|
||||||
|
}
|
||||||
|
sensor := ds18b20.New(ow)
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
println()
|
||||||
|
println("Device:", machine.Device)
|
||||||
|
|
||||||
|
println()
|
||||||
|
println("Request Temperature.")
|
||||||
|
for _, romid := range romIDs {
|
||||||
|
println("Sensor RomID: ", hex.EncodeToString(romid))
|
||||||
|
sensor.RequestTemperature(romid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait 750ms or more for DS18B20 convert T
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
println()
|
||||||
|
println("Read Temperature")
|
||||||
|
for _, romid := range romIDs {
|
||||||
|
raw, err := sensor.ReadTemperatureRaw(romid)
|
||||||
|
if err != nil {
|
||||||
|
println(err)
|
||||||
|
}
|
||||||
|
println()
|
||||||
|
println("Sensor RomID: ", hex.EncodeToString(romid))
|
||||||
|
println("Temperature Raw value: ", hex.EncodeToString(raw))
|
||||||
|
|
||||||
|
t, err := sensor.ReadTemperature(romid)
|
||||||
|
if err != nil {
|
||||||
|
println(err)
|
||||||
|
}
|
||||||
|
println("Temperature in celsius milli degrees (°C/1000): ", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# AT-CMD implementation of at-lora command set
|
||||||
|
|
||||||
|
This example implements the AT command set as used by Seeed in the LoRa-E5 series of boards, but in the form of a TinyGo program that provides a serial interface.
|
||||||
|
|
||||||
|
See https://files.seeedstudio.com/products/317990687/res/LoRa-E5%20AT%20Command%20Specification_V1.0%20.pdf for more information.
|
||||||
|
|
||||||
|
```
|
||||||
|
$ tinygo monitor
|
||||||
|
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
|
||||||
|
+AT: OK
|
||||||
|
+VER: 0.0.1 (sx127x v18)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Building
|
||||||
|
|
||||||
|
Run the following commands from the main `drivers` directory.
|
||||||
|
|
||||||
|
## Simulator
|
||||||
|
|
||||||
|
Builds/flashes atcmd console application with simulator instead of actual LoRa radio.
|
||||||
|
|
||||||
|
```
|
||||||
|
tinygo flash -target pico ./examples/lora/lorawan/atcmd/
|
||||||
|
```
|
||||||
|
|
||||||
|
## PyBadge with LoRa Featherwing
|
||||||
|
|
||||||
|
Builds/flashes atcmd console application on PyBadge using LoRa Featherwing (RFM95/SX1276).
|
||||||
|
|
||||||
|
```
|
||||||
|
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/atcmd/
|
||||||
|
```
|
||||||
|
|
||||||
|
## LoRa-E5
|
||||||
|
|
||||||
|
Builds/flashes atcmd console application on Lora-E5 using onboard SX126x.
|
||||||
|
|
||||||
|
```
|
||||||
|
tinygo flash -target lorae5 ./examples/lora/lorawan/atcmd/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Joining a Public Lorawan Network
|
||||||
|
|
||||||
|
```
|
||||||
|
AT+ID=DevEui,0101010101010101
|
||||||
|
AT+ID=AppEui,0123012301230213
|
||||||
|
AT+KEY=APPKEY,AEAEAEAEAEAEAEAAEAEAEAEAEAEAAEAE
|
||||||
|
AT+LW=NET,ON
|
||||||
|
AT+JOIN
|
||||||
|
```
|
||||||
|
|
||||||
|
AT+LW=NET,(ON|OFF) command changes Lora Sync Word to connect on public network(ON) or private networks(OFF)
|
||||||
@@ -0,0 +1,496 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/examples/lora/lorawan/common"
|
||||||
|
"tinygo.org/x/drivers/lora/lorawan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Use to test if connection to module is OK.
|
||||||
|
func quicktest() {
|
||||||
|
writeCommandOutput("AT", "OK")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check firmware version.
|
||||||
|
func version() {
|
||||||
|
writeCommandOutput("VER", common.CurrentVersion()+" ("+common.FirmwareVersion()+")")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use to check the ID of the LoRaWAN module, or change the ID.
|
||||||
|
func id(args string) error {
|
||||||
|
cmd := "ID"
|
||||||
|
|
||||||
|
// look for comma in args
|
||||||
|
param, val, hasComma := strings.Cut(args, ",")
|
||||||
|
if hasComma {
|
||||||
|
// set
|
||||||
|
data := strings.Trim(val, "\"'")
|
||||||
|
|
||||||
|
// convert data from hex formatted string
|
||||||
|
data = strings.ReplaceAll(data, " ", "")
|
||||||
|
hexdata, err := hex.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch param {
|
||||||
|
case "DevAddr":
|
||||||
|
if err := session.SetDevAddr(hexdata); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
|
||||||
|
case "DevEui":
|
||||||
|
if err := otaa.SetDevEUI(hexdata); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
|
||||||
|
case "AppEui":
|
||||||
|
if err := otaa.SetAppEUI(hexdata); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
|
||||||
|
default:
|
||||||
|
return errInvalidCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// get
|
||||||
|
switch param {
|
||||||
|
case "DevAddr":
|
||||||
|
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
|
||||||
|
case "DevEui":
|
||||||
|
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
|
||||||
|
case "AppEui":
|
||||||
|
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
|
||||||
|
default:
|
||||||
|
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
|
||||||
|
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
|
||||||
|
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use to reset the module. If module returns error, then reset function is invalid.
|
||||||
|
func reset() error {
|
||||||
|
radio.Reset()
|
||||||
|
|
||||||
|
writeCommandOutput("RESET", "OK")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use to send string format frame which is no need to be confirmed by the server.
|
||||||
|
func msg(data string) error {
|
||||||
|
cmd := "MSG"
|
||||||
|
writeCommandOutput(cmd, "Start")
|
||||||
|
|
||||||
|
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use to send string format frame which must be confirmed by the server
|
||||||
|
func cmsg(data string) error {
|
||||||
|
cmd := "CMSG"
|
||||||
|
writeCommandOutput(cmd, "Start")
|
||||||
|
|
||||||
|
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: confirmation
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use to send hex format frame which is no need to be confirmed by the server
|
||||||
|
func msghex(data string) error {
|
||||||
|
cmd := "MSGHEX"
|
||||||
|
writeCommandOutput(cmd, "Start")
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use to send hex format frame which must be confirmed by the server.
|
||||||
|
func cmsghex(data string) error {
|
||||||
|
cmd := "CMSGHEX"
|
||||||
|
writeCommandOutput(cmd, "Start")
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use to send string format LoRaWAN proprietary frames
|
||||||
|
func pmsg(data string) error {
|
||||||
|
cmd := "PMSG"
|
||||||
|
writeCommandOutput(cmd, "Start")
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use to send hex format LoRaWAN proprietary frames.
|
||||||
|
func pmsghex(data string) error {
|
||||||
|
cmd := "PMSGHEX"
|
||||||
|
writeCommandOutput(cmd, "Start")
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set PORT number which will be used by MSG/CMSG/MSGHEX/CMSGHEX command to send
|
||||||
|
// message, port number should range from 1 to 255. User should refer to LoRaWAN
|
||||||
|
// specification to choose port.
|
||||||
|
func port(p string) error {
|
||||||
|
cmd := "PMSG"
|
||||||
|
writeCommandOutput(cmd, p)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set ADR function of LoRaWAN module
|
||||||
|
func adr(state string) error {
|
||||||
|
cmd := "ADR"
|
||||||
|
writeCommandOutput(cmd, state)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use LoRaWAN defined DRx to set datarate of LoRaWAN AT modem.
|
||||||
|
func dr(rate string) error {
|
||||||
|
cmd := "DR"
|
||||||
|
writeCommandOutput(cmd, rate)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Channel Configuration
|
||||||
|
func ch(channel string) error {
|
||||||
|
cmd := "CH"
|
||||||
|
writeCommandOutput(cmd, channel)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set and Check Power
|
||||||
|
func power(setting string) error {
|
||||||
|
cmd := "POWER"
|
||||||
|
writeCommandOutput(cmd, setting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unconfirmed message repeats times.
|
||||||
|
func rept(setting string) error {
|
||||||
|
cmd := "REPT"
|
||||||
|
writeCommandOutput(cmd, setting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirmed message retry times. Valid range 0~254,
|
||||||
|
// if retry times is less than 2, only one message will
|
||||||
|
// be sent. Random delay 3 - 10s between each retry
|
||||||
|
// (band duty cycle limitation has the priority)
|
||||||
|
func retry(setting string) error {
|
||||||
|
cmd := "RETRY"
|
||||||
|
writeCommandOutput(cmd, setting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rxwin2(setting string) error {
|
||||||
|
cmd := "RXWIN2"
|
||||||
|
writeCommandOutput(cmd, setting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rxwin1(setting string) error {
|
||||||
|
cmd := "RXWIN1"
|
||||||
|
writeCommandOutput(cmd, setting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func key(setting string) error {
|
||||||
|
cmd := "KEY"
|
||||||
|
|
||||||
|
// look for comma in args
|
||||||
|
param, val, hasComma := strings.Cut(setting, ",")
|
||||||
|
if hasComma {
|
||||||
|
// set
|
||||||
|
data := strings.Trim(val, "\"'")
|
||||||
|
|
||||||
|
// convert data from hex formatted string
|
||||||
|
data = strings.ReplaceAll(data, " ", "")
|
||||||
|
hexdata, err := hex.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch param {
|
||||||
|
case "APPKEY":
|
||||||
|
if err := otaa.SetAppKey(hexdata); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "APPKEY, "+otaa.GetAppKey())
|
||||||
|
default:
|
||||||
|
return errInvalidCommand
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cannot get keys
|
||||||
|
return errInvalidCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
func fdefault(setting string) error {
|
||||||
|
cmd := "FDEFAULT"
|
||||||
|
writeCommandOutput(cmd, "OK")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mode(setting string) error {
|
||||||
|
cmd := "MODE"
|
||||||
|
writeCommandOutput(cmd, setting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func join(setting string) error {
|
||||||
|
// TODO: check that DevEUI, AppEUI, and AppKey have values
|
||||||
|
|
||||||
|
cmd := "JOIN"
|
||||||
|
writeCommandOutput(cmd, "Starting")
|
||||||
|
if err := lorawan.Join(otaa, session); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Network joined")
|
||||||
|
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
|
||||||
|
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
|
||||||
|
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
|
||||||
|
writeCommandOutput(cmd, "NetID, "+otaa.GetNetID())
|
||||||
|
writeCommandOutput(cmd, "NwkSKey, "+session.GetNwkSKey())
|
||||||
|
writeCommandOutput(cmd, "AppSKey, "+session.GetAppSKey())
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func beacon(setting string) error {
|
||||||
|
cmd := "BEACON"
|
||||||
|
writeCommandOutput(cmd, "Starting")
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func class(setting string) error {
|
||||||
|
cmd := "CLASS"
|
||||||
|
writeCommandOutput(cmd, "Starting")
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func delay(setting string) error {
|
||||||
|
cmd := "DELAY"
|
||||||
|
writeCommandOutput(cmd, setting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lw(setting string) error {
|
||||||
|
cmd := "LW"
|
||||||
|
|
||||||
|
param, val, hasComma := strings.Cut(setting, ",")
|
||||||
|
if hasComma {
|
||||||
|
if param == "NET" {
|
||||||
|
if val == "ON" {
|
||||||
|
lorawan.SetPublicNetwork(true)
|
||||||
|
} else {
|
||||||
|
lorawan.SetPublicNetwork(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeCommandOutput(cmd, setting)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wdt(setting string) error {
|
||||||
|
cmd := "WDT"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lowpower(setting string) error {
|
||||||
|
cmd := "LOWPOWER"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func vdd(setting string) error {
|
||||||
|
cmd := "VDD"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func temp(setting string) error {
|
||||||
|
cmd := "TEMP"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtc(setting string) error {
|
||||||
|
cmd := "RTC"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func eeprom(setting string) error {
|
||||||
|
cmd := "EEPROM"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func uartcmd(setting string) error {
|
||||||
|
cmd := "UART"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func test(setting string) error {
|
||||||
|
cmd := "TEST"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func log(setting string) error {
|
||||||
|
cmd := "LOG"
|
||||||
|
writeCommandOutput(cmd, "Not implemented")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func send(data string) error {
|
||||||
|
cmd := "SEND"
|
||||||
|
writeCommandOutput(cmd, "Start")
|
||||||
|
|
||||||
|
// remove leading/trailing quotes
|
||||||
|
data = strings.Trim(data, "\"'")
|
||||||
|
|
||||||
|
if err := radio.Tx([]byte(data), defaultTimeout); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendhex(data string) error {
|
||||||
|
cmd := "SENDHEX"
|
||||||
|
writeCommandOutput(cmd, "Start")
|
||||||
|
|
||||||
|
// remove leading/trailing quotes
|
||||||
|
data = strings.Trim(data, "\"'")
|
||||||
|
|
||||||
|
// convert data from hex formatted string
|
||||||
|
data = strings.ReplaceAll(data, " ", "")
|
||||||
|
payload, err := hex.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := radio.Tx(payload, defaultTimeout); err != nil {
|
||||||
|
writeCommandOutput(cmd, err.Error())
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandOutput(cmd, "Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func recv(setting string) error {
|
||||||
|
cmd := "RECV"
|
||||||
|
|
||||||
|
data, err := common.Lorarx()
|
||||||
|
if err != nil {
|
||||||
|
writeCommandOutput(cmd, "ERROR "+err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writeCommandOutput(cmd, string(data))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvhex(setting string) error {
|
||||||
|
cmd := "RECVHEX"
|
||||||
|
|
||||||
|
data, err := common.Lorarx()
|
||||||
|
if err != nil {
|
||||||
|
writeCommandOutput(cmd, "ERROR "+err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
writeCommandOutput(cmd, string(data))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func crlf() {
|
||||||
|
uart.Write([]byte("\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCommandOutput(cmd, data string) {
|
||||||
|
uart.Write([]byte("+" + cmd + ": "))
|
||||||
|
uart.Write([]byte(data))
|
||||||
|
crlf()
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// AT command set console running on the device UART to communicate with
|
||||||
|
// an attached LoRa device.
|
||||||
|
//
|
||||||
|
// Computer <-> UART <-> MCU <-> SPI <-> SX126x/SX127x
|
||||||
|
//
|
||||||
|
// Connect using default baudrate for this hardware, 8-N-1 with your terminal program.
|
||||||
|
// For details on the AT command set, see:
|
||||||
|
// https://files.seeedstudio.com/products/317990687/res/LoRa-E5%20AT%20Command%20Specification_V1.0%20.pdf
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/examples/lora/lorawan/common"
|
||||||
|
"tinygo.org/x/drivers/lora"
|
||||||
|
"tinygo.org/x/drivers/lora/lorawan"
|
||||||
|
"tinygo.org/x/drivers/lora/lorawan/region"
|
||||||
|
)
|
||||||
|
|
||||||
|
// change these to test a different UART or pins if available
|
||||||
|
var (
|
||||||
|
uart = machine.Serial
|
||||||
|
tx = machine.UART_TX_PIN
|
||||||
|
rx = machine.UART_RX_PIN
|
||||||
|
input = make([]byte, 0, 64)
|
||||||
|
|
||||||
|
radio lora.Radio
|
||||||
|
session *lorawan.Session
|
||||||
|
otaa *lorawan.Otaa
|
||||||
|
|
||||||
|
defaultTimeout uint32 = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||||
|
|
||||||
|
var err error
|
||||||
|
radio, err = common.SetupLora()
|
||||||
|
if err != nil {
|
||||||
|
fail(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
session = &lorawan.Session{}
|
||||||
|
otaa = &lorawan.Otaa{}
|
||||||
|
lorawan.UseRadio(radio)
|
||||||
|
|
||||||
|
lorawan.UseRegionSettings(region.EU868())
|
||||||
|
|
||||||
|
for {
|
||||||
|
if uart.Buffered() > 0 {
|
||||||
|
data, _ := uart.ReadByte()
|
||||||
|
|
||||||
|
switch data {
|
||||||
|
case 13:
|
||||||
|
// return key
|
||||||
|
if err := parse(input); err != nil {
|
||||||
|
uart.Write([]byte("ERROR: "))
|
||||||
|
uart.Write([]byte(err.Error()))
|
||||||
|
crlf()
|
||||||
|
}
|
||||||
|
input = input[:0]
|
||||||
|
default:
|
||||||
|
// just capture the character
|
||||||
|
input = append(input, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fail(msg string) {
|
||||||
|
for {
|
||||||
|
uart.Write([]byte(msg))
|
||||||
|
crlf()
|
||||||
|
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errInvalidCommand = errors.New("Invalid command")
|
||||||
|
)
|
||||||
|
|
||||||
|
func parse(data []byte) error {
|
||||||
|
switch {
|
||||||
|
case len(data) < 2, string(data[0:2]) != "AT":
|
||||||
|
return errInvalidCommand
|
||||||
|
case len(data) == 2:
|
||||||
|
// just the AT command by itself
|
||||||
|
quicktest()
|
||||||
|
case len(data) < 6 || data[2] != '+':
|
||||||
|
return errInvalidCommand
|
||||||
|
default:
|
||||||
|
// parse the rest of the command
|
||||||
|
cmd, args, _ := strings.Cut(string(data[3:]), "=")
|
||||||
|
return parseCommand(cmd, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCommand(cmd, args string) error {
|
||||||
|
switch cmd {
|
||||||
|
case "VER":
|
||||||
|
version()
|
||||||
|
case "ID":
|
||||||
|
id(args)
|
||||||
|
case "RESET":
|
||||||
|
reset()
|
||||||
|
case "MSG":
|
||||||
|
msg(args)
|
||||||
|
case "CMSG":
|
||||||
|
cmsg(args)
|
||||||
|
case "MSGHEX":
|
||||||
|
msghex(args)
|
||||||
|
case "CMSGHEX":
|
||||||
|
cmsghex(args)
|
||||||
|
case "PMSG":
|
||||||
|
pmsg(args)
|
||||||
|
case "PMSGHEX":
|
||||||
|
pmsg(args)
|
||||||
|
case "PORT":
|
||||||
|
port(args)
|
||||||
|
case "ADR":
|
||||||
|
adr(args)
|
||||||
|
case "DR":
|
||||||
|
dr(args)
|
||||||
|
case "CH":
|
||||||
|
ch(args)
|
||||||
|
case "POWER":
|
||||||
|
power(args)
|
||||||
|
case "REPT":
|
||||||
|
rept(args)
|
||||||
|
case "RETRY":
|
||||||
|
retry(args)
|
||||||
|
case "RXWIN2":
|
||||||
|
rxwin2(args)
|
||||||
|
case "RXWIN1":
|
||||||
|
rxwin1(args)
|
||||||
|
case "KEY":
|
||||||
|
key(args)
|
||||||
|
case "FDEFAULT":
|
||||||
|
fdefault(args)
|
||||||
|
case "MODE":
|
||||||
|
mode(args)
|
||||||
|
case "JOIN":
|
||||||
|
join(args)
|
||||||
|
case "BEACON":
|
||||||
|
join(args)
|
||||||
|
case "CLASS":
|
||||||
|
class(args)
|
||||||
|
case "DELAY":
|
||||||
|
delay(args)
|
||||||
|
case "LW":
|
||||||
|
lw(args)
|
||||||
|
case "WDT":
|
||||||
|
wdt(args)
|
||||||
|
case "LOWPOWER":
|
||||||
|
lowpower(args)
|
||||||
|
case "VDD":
|
||||||
|
vdd(args)
|
||||||
|
case "TEMP":
|
||||||
|
temp(args)
|
||||||
|
case "RTC":
|
||||||
|
rtc(args)
|
||||||
|
case "EEPROM":
|
||||||
|
eeprom(args)
|
||||||
|
case "UART":
|
||||||
|
uartcmd(args)
|
||||||
|
case "TEST":
|
||||||
|
test(args)
|
||||||
|
case "LOG":
|
||||||
|
log(args)
|
||||||
|
case "RECV":
|
||||||
|
recv(args)
|
||||||
|
case "RECVHEX":
|
||||||
|
recvhex(args)
|
||||||
|
case "SEND":
|
||||||
|
send(args)
|
||||||
|
case "SENDHEX":
|
||||||
|
sendhex(args)
|
||||||
|
default:
|
||||||
|
return errInvalidCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Simple Lorawan example
|
||||||
|
|
||||||
|
This demo code will connect Lorawan network and send sample uplink message
|
||||||
|
|
||||||
|
You may change your Lorawan keys (AppEUI, DevEUI, AppKEY) in key-default.go
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
$ tinygo monitor
|
||||||
|
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
|
||||||
|
Lorawan Simple Demo
|
||||||
|
Start Lorawan Join sequence
|
||||||
|
loraConnect: Connected !
|
||||||
|
```
|
||||||
|
|
||||||
|
# Building
|
||||||
|
|
||||||
|
## Simulator
|
||||||
|
|
||||||
|
```
|
||||||
|
tinygo flash -target pico ./examples/lora/lorawan/basic-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
## PyBadge with LoRa Featherwing
|
||||||
|
|
||||||
|
```
|
||||||
|
tinygo flash -target pybadge -tags featherwing ./examples/lora/lorawan/basic-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
## LoRa-E5
|
||||||
|
|
||||||
|
```
|
||||||
|
tinygo flash -target lorae5 ./examples/lora/lorawan/basic-demo
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Enable debugging
|
||||||
|
|
||||||
|
You can also enable some debug logs with ldflags :
|
||||||
|
|
||||||
|
```
|
||||||
|
$ tinygo build -ldflags="-X 'main.debug=true'" -target=lorae5
|
||||||
|
```
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
//go:build !customkeys
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tinygo.org/x/drivers/lora/lorawan"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are sample keys, so the example builds
|
||||||
|
// Either change here, or create a new go file and use customkeys build tag
|
||||||
|
func setLorawanKeys() {
|
||||||
|
otaa.SetAppEUI([]uint8{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
|
||||||
|
otaa.SetDevEUI([]uint8{0xB3, 0xD5, 0x41, 0x00, 0x0A, 0xF1, 0xA4, 0x45})
|
||||||
|
otaa.SetAppKey([]uint8{0x12, 0x22, 0xA3, 0xFF, 0x0C, 0x7B, 0x76, 0x7B, 0x8F, 0xD3, 0x12, 0x4F, 0xCE, 0x7A, 0x32, 0x16})
|
||||||
|
lorawan.SetPublicNetwork(true)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// Simple code for connecting to Lorawan network and uploading sample payload
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/examples/lora/lorawan/common"
|
||||||
|
"tinygo.org/x/drivers/lora"
|
||||||
|
"tinygo.org/x/drivers/lora/lorawan"
|
||||||
|
"tinygo.org/x/drivers/lora/lorawan/region"
|
||||||
|
)
|
||||||
|
|
||||||
|
var debug string
|
||||||
|
|
||||||
|
const (
|
||||||
|
LORAWAN_JOIN_TIMEOUT_SEC = 180
|
||||||
|
LORAWAN_RECONNECT_DELAY_SEC = 15
|
||||||
|
LORAWAN_UPLINK_DELAY_SEC = 60
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
radio lora.Radio
|
||||||
|
session *lorawan.Session
|
||||||
|
otaa *lorawan.Otaa
|
||||||
|
)
|
||||||
|
|
||||||
|
func loraConnect() error {
|
||||||
|
start := time.Now()
|
||||||
|
var err error
|
||||||
|
for time.Since(start) < LORAWAN_JOIN_TIMEOUT_SEC*time.Second {
|
||||||
|
println("Trying to join network")
|
||||||
|
err = lorawan.Join(otaa, session)
|
||||||
|
if err == nil {
|
||||||
|
println("Connected to network !")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
println("Join error:", err, "retrying in", LORAWAN_RECONNECT_DELAY_SEC, "sec")
|
||||||
|
time.Sleep(time.Second * LORAWAN_RECONNECT_DELAY_SEC)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = errors.New("Unable to join Lorawan network")
|
||||||
|
println(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func failMessage(err error) {
|
||||||
|
println("FATAL:", err)
|
||||||
|
for {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
println("*** Lorawan basic join and uplink demo ***")
|
||||||
|
|
||||||
|
// Board specific Lorawan initialization
|
||||||
|
var err error
|
||||||
|
radio, err = common.SetupLora()
|
||||||
|
if err != nil {
|
||||||
|
failMessage(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required for LoraWan operations
|
||||||
|
session = &lorawan.Session{}
|
||||||
|
otaa = &lorawan.Otaa{}
|
||||||
|
|
||||||
|
// Connect the lorawan with the Lora Radio device.
|
||||||
|
lorawan.UseRadio(radio)
|
||||||
|
|
||||||
|
lorawan.UseRegionSettings(region.EU868())
|
||||||
|
|
||||||
|
// Configure AppEUI, DevEUI, APPKey, and public/private Lorawan Network
|
||||||
|
setLorawanKeys()
|
||||||
|
|
||||||
|
if debug != "" {
|
||||||
|
println("main: Network joined")
|
||||||
|
println("main: DevEui, " + otaa.GetDevEUI())
|
||||||
|
println("main: AppEui, " + otaa.GetAppEUI())
|
||||||
|
println("main: DevAddr, " + otaa.GetAppKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to connect Lorawan network
|
||||||
|
if err := loraConnect(); err != nil {
|
||||||
|
failMessage(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug != "" {
|
||||||
|
println("main: NetID, " + otaa.GetNetID())
|
||||||
|
println("main: NwkSKey, " + session.GetNwkSKey())
|
||||||
|
println("main: AppSKey, " + session.GetAppSKey())
|
||||||
|
println("main: Done")
|
||||||
|
}
|
||||||
|
// Try to periodicaly send an uplink sample message
|
||||||
|
upCount := 1
|
||||||
|
for {
|
||||||
|
payload := "Hello TinyGo #" + strconv.Itoa(upCount)
|
||||||
|
|
||||||
|
if err := lorawan.SendUplink([]byte(payload), session); err != nil {
|
||||||
|
println("Uplink error:", err)
|
||||||
|
} else {
|
||||||
|
println("Uplink success, msg=", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Sleeping for", LORAWAN_UPLINK_DELAY_SEC, "sec")
|
||||||
|
time.Sleep(time.Second * LORAWAN_UPLINK_DELAY_SEC)
|
||||||
|
upCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//go:build featherwing
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "machine"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// We assume LoRa Featherwing module with sx127x is connected to PyBadge
|
||||||
|
rstPin = machine.D11
|
||||||
|
csPin = machine.D10
|
||||||
|
dio0Pin = machine.D6
|
||||||
|
dio1Pin = machine.D9
|
||||||
|
spi = machine.SPI0
|
||||||
|
)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//go:build lgt92
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "machine"
|
||||||
|
|
||||||
|
var (
|
||||||
|
rstPin = machine.PB0
|
||||||
|
csPin = machine.PA15
|
||||||
|
dio0Pin = machine.PC13
|
||||||
|
dio1Pin = machine.PB10
|
||||||
|
spi = machine.SPI0
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errRadioNotFound = errors.New("radio not found")
|
||||||
|
errRxTimeout = errors.New("radio RX timeout")
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//go:build !featherwing && !lgt92 && !stm32wlx && !sx126x
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import "tinygo.org/x/drivers/lora"
|
||||||
|
|
||||||
|
// do simulator setup here
|
||||||
|
func SetupLora() (lora.Radio, error) {
|
||||||
|
return &SimLoraRadio{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimLoraRadio struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SimLoraRadio) Reset() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SimLoraRadio) Tx(pkt []uint8, timeoutMs uint32) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SimLoraRadio) Rx(timeoutMs uint32) ([]uint8, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SimLoraRadio) SetFrequency(freq uint32) {}
|
||||||
|
func (sr *SimLoraRadio) SetIqMode(mode uint8) {}
|
||||||
|
func (sr *SimLoraRadio) SetCodingRate(cr uint8) {}
|
||||||
|
func (sr *SimLoraRadio) SetBandwidth(bw uint8) {}
|
||||||
|
func (sr *SimLoraRadio) SetCrc(enable bool) {}
|
||||||
|
func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {}
|
||||||
|
func (sr *SimLoraRadio) LoraConfig(cnf lora.Config) {}
|
||||||
|
|
||||||
|
func FirmwareVersion() string {
|
||||||
|
return "simulator " + CurrentVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lorarx() ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//go:build stm32wlx
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/lora"
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FREQ = 868100000
|
||||||
|
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||||
|
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loraRadio *sx126x.Device
|
||||||
|
)
|
||||||
|
|
||||||
|
var spi = machine.SPI3
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl()
|
||||||
|
}
|
||||||
|
|
||||||
|
// do sx126x setup here
|
||||||
|
func SetupLora() (lora.Radio, error) {
|
||||||
|
loraRadio = sx126x.New(spi)
|
||||||
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
|
// Create radio controller for target
|
||||||
|
loraRadio.SetRadioController(newRadioControl())
|
||||||
|
|
||||||
|
if state := loraRadio.DetectDevice(); !state {
|
||||||
|
return nil, errRadioNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return loraRadio, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FirmwareVersion() string {
|
||||||
|
return "sx126x"
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lorarx() ([]byte, error) {
|
||||||
|
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
//go:build !stm32wlx && sx126x
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/lora"
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FREQ = 868100000
|
||||||
|
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||||
|
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loraRadio *sx126x.Device
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
spi = machine.SPI0
|
||||||
|
nssPin, busyPin, dio1Pin = machine.GP17, machine.GP10, machine.GP11
|
||||||
|
rxPin, txLowPin, txHighPin = machine.GP13, machine.GP12, machine.GP12
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl(nssPin, busyPin, dio1Pin, rxPin, txLowPin, txHighPin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// do sx126x setup here
|
||||||
|
func SetupLora() (lora.Radio, error) {
|
||||||
|
loraRadio = sx126x.New(spi)
|
||||||
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
|
// Create radio controller for target
|
||||||
|
loraRadio.SetRadioController(newRadioControl())
|
||||||
|
|
||||||
|
if state := loraRadio.DetectDevice(); !state {
|
||||||
|
return nil, errRadioNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return loraRadio, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FirmwareVersion() string {
|
||||||
|
return "sx126x"
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lorarx() ([]byte, error) {
|
||||||
|
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
//go:build featherwing || lgt92
|
||||||
|
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/lora"
|
||||||
|
"tinygo.org/x/drivers/sx127x"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FREQ = 868100000
|
||||||
|
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||||
|
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loraRadio *sx127x.Device
|
||||||
|
)
|
||||||
|
|
||||||
|
// do sx127x setup here
|
||||||
|
func SetupLora() (lora.Radio, error) {
|
||||||
|
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
spi.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
|
||||||
|
|
||||||
|
loraRadio = sx127x.New(spi, rstPin)
|
||||||
|
loraRadio.SetRadioController(sx127x.NewRadioControl(csPin, dio0Pin, dio1Pin))
|
||||||
|
loraRadio.Reset()
|
||||||
|
|
||||||
|
if state := loraRadio.DetectDevice(); !state {
|
||||||
|
return nil, errRadioNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return loraRadio, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FirmwareVersion() string {
|
||||||
|
v := loraRadio.GetVersion()
|
||||||
|
return "sx127x v" + strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lorarx() ([]byte, error) {
|
||||||
|
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
const VERSION = "0.0.1"
|
||||||
|
|
||||||
|
func CurrentVersion() string {
|
||||||
|
return VERSION
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
// Connects to an MPU6886 I2C accelerometer/gyroscope.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/mpu6886"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
machine.I2C0.Configure(machine.I2CConfig{})
|
||||||
|
|
||||||
|
accel := mpu6886.New(machine.I2C0)
|
||||||
|
accel.Configure(mpu6886.Config{})
|
||||||
|
|
||||||
|
for {
|
||||||
|
x, y, z, _ := accel.ReadAcceleration()
|
||||||
|
println(x, y, z)
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"time"
|
||||||
|
"tinygo.org/x/drivers/onewire"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
pin := machine.D2
|
||||||
|
|
||||||
|
ow := onewire.New(pin)
|
||||||
|
|
||||||
|
for {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
println()
|
||||||
|
println("Device:", machine.Device)
|
||||||
|
|
||||||
|
romIDs, err := ow.Search(onewire.SEARCH)
|
||||||
|
if err != nil {
|
||||||
|
println(err)
|
||||||
|
}
|
||||||
|
for _, romid := range romIDs {
|
||||||
|
println(hex.EncodeToString(romid))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(romIDs) == 1 {
|
||||||
|
// only 1 device on bus
|
||||||
|
r, err := ow.ReadAddress()
|
||||||
|
if err != nil {
|
||||||
|
println(err)
|
||||||
|
}
|
||||||
|
println(hex.EncodeToString(r))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,542 +0,0 @@
|
|||||||
//go:build tinygo
|
|
||||||
|
|
||||||
package console
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"machine"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/tinyfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
const consoleBufLen = 64
|
|
||||||
|
|
||||||
const startBlock = 0
|
|
||||||
const blockCount = 0
|
|
||||||
|
|
||||||
var (
|
|
||||||
debug = false
|
|
||||||
|
|
||||||
input [consoleBufLen]byte
|
|
||||||
|
|
||||||
console = machine.Serial
|
|
||||||
readyLED = machine.LED
|
|
||||||
|
|
||||||
flashdev tinyfs.BlockDevice
|
|
||||||
fs tinyfs.Filesystem
|
|
||||||
|
|
||||||
currdir = "/"
|
|
||||||
|
|
||||||
commands = map[string]cmdfunc{
|
|
||||||
"": noop,
|
|
||||||
"dbg": dbg,
|
|
||||||
"help": help,
|
|
||||||
"lsblk": lsblk,
|
|
||||||
"mount": mount,
|
|
||||||
"umount": umount,
|
|
||||||
"format": format,
|
|
||||||
"xxd": xxd,
|
|
||||||
"ls": ls,
|
|
||||||
"samples": samples,
|
|
||||||
"mkdir": mkdir,
|
|
||||||
"cat": cat,
|
|
||||||
"create": create,
|
|
||||||
"write": write,
|
|
||||||
"rm": rm,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type cmdfunc func(argv []string)
|
|
||||||
|
|
||||||
const (
|
|
||||||
StateInput = iota
|
|
||||||
StateEscape
|
|
||||||
StateEscBrc
|
|
||||||
StateCSI
|
|
||||||
)
|
|
||||||
|
|
||||||
func RunFor(dev tinyfs.BlockDevice, filesys tinyfs.Filesystem) {
|
|
||||||
flashdev = dev
|
|
||||||
fs = filesys
|
|
||||||
|
|
||||||
readyLED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
readyLED.High()
|
|
||||||
|
|
||||||
readyLED.Low()
|
|
||||||
println("SPI Configured. Reading flash info")
|
|
||||||
|
|
||||||
/*
|
|
||||||
lfsConfig := flashlfs.NewConfig()
|
|
||||||
if blockCount == 0 {
|
|
||||||
lfsConfig.BlockCount = (flashdev.Attrs().TotalSize / lfsConfig.BlockSize) - startBlock
|
|
||||||
} else {
|
|
||||||
lfsConfig.BlockCount = blockCount
|
|
||||||
}
|
|
||||||
println("Start block:", startBlock)
|
|
||||||
println("Block count:", lfsConfig.BlockCount)
|
|
||||||
|
|
||||||
blockdev = flashlfs.NewBlockDevice(flashdev, startBlock, lfsConfig.BlockSize)
|
|
||||||
*/
|
|
||||||
|
|
||||||
prompt()
|
|
||||||
|
|
||||||
var state = StateInput
|
|
||||||
|
|
||||||
for i := 0; ; {
|
|
||||||
if console.Buffered() > 0 {
|
|
||||||
data, _ := console.ReadByte()
|
|
||||||
if debug {
|
|
||||||
fmt.Printf("\rdata: %x\r\n\r", data)
|
|
||||||
prompt()
|
|
||||||
console.Write(input[:i])
|
|
||||||
}
|
|
||||||
switch state {
|
|
||||||
case StateInput:
|
|
||||||
switch data {
|
|
||||||
case 0x8:
|
|
||||||
fallthrough
|
|
||||||
case 0x7f: // this is probably wrong... works on my machine tho :)
|
|
||||||
// backspace
|
|
||||||
if i > 0 {
|
|
||||||
i -= 1
|
|
||||||
console.Write([]byte{0x8, 0x20, 0x8})
|
|
||||||
}
|
|
||||||
case 13:
|
|
||||||
// return key
|
|
||||||
if console.Buffered() > 0 {
|
|
||||||
data, _ := console.ReadByte()
|
|
||||||
if data != 10 {
|
|
||||||
println("\r\nunexpected: \r", int(data))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.Write([]byte("\r\n"))
|
|
||||||
runCommand(string(input[:i]))
|
|
||||||
prompt()
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
continue
|
|
||||||
case 27:
|
|
||||||
// escape
|
|
||||||
state = StateEscape
|
|
||||||
default:
|
|
||||||
// anything else, just echo the character if it is printable
|
|
||||||
if strconv.IsPrint(rune(data)) {
|
|
||||||
if i < (consoleBufLen - 1) {
|
|
||||||
console.WriteByte(data)
|
|
||||||
input[i] = data
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case StateEscape:
|
|
||||||
switch data {
|
|
||||||
case 0x5b:
|
|
||||||
state = StateEscBrc
|
|
||||||
default:
|
|
||||||
state = StateInput
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// TODO: handle escape sequences
|
|
||||||
state = StateInput
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func runCommand(line string) {
|
|
||||||
argv := strings.SplitN(strings.TrimSpace(line), " ", -1)
|
|
||||||
cmd := argv[0]
|
|
||||||
cmdfn, ok := commands[cmd]
|
|
||||||
if !ok {
|
|
||||||
println("unknown command: " + line)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cmdfn(argv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func noop(argv []string) {}
|
|
||||||
|
|
||||||
func help(argv []string) {
|
|
||||||
fmt.Printf("help\r\n")
|
|
||||||
fmt.Printf(" show help\r\n")
|
|
||||||
fmt.Printf("dbg\r\n")
|
|
||||||
fmt.Printf(" toggle debug mode\r\n")
|
|
||||||
fmt.Printf("xxd <hex address, ex: 0xA0> <size of hexdump in bytes>\r\n")
|
|
||||||
fmt.Printf(" hexdump the specified address\r\n")
|
|
||||||
fmt.Printf("ls <target file>\r\n")
|
|
||||||
fmt.Printf(" list information\r\n")
|
|
||||||
fmt.Printf("samples\r\n")
|
|
||||||
fmt.Printf(" write some files in the root directory\r\n")
|
|
||||||
fmt.Printf("mkdir <target dir>\r\n")
|
|
||||||
fmt.Printf(" create directory\r\n")
|
|
||||||
fmt.Printf("cat <target file>\r\n")
|
|
||||||
fmt.Printf(" print the contents of file\r\n")
|
|
||||||
fmt.Printf("create <target file>\r\n")
|
|
||||||
fmt.Printf(" create file\r\n")
|
|
||||||
fmt.Printf("write <target file>\r\n")
|
|
||||||
fmt.Printf(" write to file (press CTRL-D to exit)\r\n")
|
|
||||||
fmt.Printf("rm\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func dbg(argv []string) {
|
|
||||||
if debug {
|
|
||||||
debug = false
|
|
||||||
println("Console debugging off")
|
|
||||||
} else {
|
|
||||||
debug = true
|
|
||||||
println("Console debugging on")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lsblk(argv []string) {
|
|
||||||
fmt.Printf("lsblk : not implement\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func mount(argv []string) {
|
|
||||||
if err := fs.Mount(); err != nil {
|
|
||||||
println("Could not mount LittleFS filesystem: " + err.Error() + "\r\n")
|
|
||||||
} else {
|
|
||||||
println("Successfully mounted LittleFS filesystem.\r\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func format(argv []string) {
|
|
||||||
if err := fs.Format(); err != nil {
|
|
||||||
println("Could not format LittleFS filesystem: " + err.Error() + "\r\n")
|
|
||||||
} else {
|
|
||||||
println("Successfully formatted LittleFS filesystem.\r\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func umount(argv []string) {
|
|
||||||
if err := fs.Unmount(); err != nil {
|
|
||||||
println("Could not unmount LittleFS filesystem: " + err.Error() + "\r\n")
|
|
||||||
} else {
|
|
||||||
println("Successfully unmounted LittleFS filesystem.\r\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
var err error
|
|
||||||
if fatfs == nil {
|
|
||||||
fatfs, err = fat.New(fatdisk)
|
|
||||||
if err != nil {
|
|
||||||
fatfs = nil
|
|
||||||
println("could not load FAT filesystem: " + err.Error() + "\r\n")
|
|
||||||
}
|
|
||||||
fmt.Printf("loaded fs\r\n")
|
|
||||||
}
|
|
||||||
if rootdir == nil {
|
|
||||||
rootdir, err = fatfs.RootDir()
|
|
||||||
if err != nil {
|
|
||||||
rootdir = nil
|
|
||||||
println("could not load rootdir: " + err.Error() + "\r\n")
|
|
||||||
}
|
|
||||||
fmt.Printf("loaded rootdir\r\n")
|
|
||||||
}
|
|
||||||
if currdir == nil {
|
|
||||||
currdir = rootdir
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func ls(argv []string) {
|
|
||||||
path := "/"
|
|
||||||
if len(argv) > 1 {
|
|
||||||
path = strings.TrimSpace(argv[1])
|
|
||||||
}
|
|
||||||
dir, err := fs.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Could not open directory %s: %v\r\n", path, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer dir.Close()
|
|
||||||
infos, err := dir.Readdir(0)
|
|
||||||
_ = infos
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Could not read directory %s: %v\r\n", path, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, info := range infos {
|
|
||||||
s := "-rwxrwxrwx"
|
|
||||||
if info.IsDir() {
|
|
||||||
s = "drwxrwxrwx"
|
|
||||||
}
|
|
||||||
fmt.Printf("%s %5d %s\r\n", s, info.Size(), info.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkdir(argv []string) {
|
|
||||||
tgt := ""
|
|
||||||
if len(argv) == 2 {
|
|
||||||
tgt = strings.TrimSpace(argv[1])
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Trying mkdir to " + tgt)
|
|
||||||
}
|
|
||||||
if tgt == "" {
|
|
||||||
println("Usage: mkdir <target dir>")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := fs.Mkdir(tgt, 0777)
|
|
||||||
if err != nil {
|
|
||||||
println("Could not mkdir " + tgt + ": " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func rm(argv []string) {
|
|
||||||
tgt := ""
|
|
||||||
if len(argv) == 2 {
|
|
||||||
tgt = strings.TrimSpace(argv[1])
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Trying rm to " + tgt)
|
|
||||||
}
|
|
||||||
if tgt == "" {
|
|
||||||
println("Usage: rm <target dir>")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := fs.Remove(tgt)
|
|
||||||
if err != nil {
|
|
||||||
println("Could not rm " + tgt + ": " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func samples(argv []string) {
|
|
||||||
buf := make([]byte, 90)
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
name := fmt.Sprintf("file%d.txt", i)
|
|
||||||
if bytes, err := createSampleFile(name, buf); err != nil {
|
|
||||||
fmt.Printf("%s\r\n", err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
fmt.Printf("wrote %d bytes to %s\r\n", bytes, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func create(argv []string) {
|
|
||||||
tgt := ""
|
|
||||||
if len(argv) == 2 {
|
|
||||||
tgt = strings.TrimSpace(argv[1])
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Trying create to " + tgt)
|
|
||||||
}
|
|
||||||
buf := make([]byte, 90)
|
|
||||||
if bytes, err := createSampleFile(tgt, buf); err != nil {
|
|
||||||
fmt.Printf("%s\r\n", err)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
fmt.Printf("wrote %d bytes to %s\r\n", bytes, tgt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func write(argv []string) {
|
|
||||||
tgt := ""
|
|
||||||
if len(argv) == 2 {
|
|
||||||
tgt = strings.TrimSpace(argv[1])
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Trying receive to " + tgt)
|
|
||||||
}
|
|
||||||
buf := make([]byte, 1)
|
|
||||||
f, err := fs.OpenFile(tgt, os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("error opening %s: %s\r\n", tgt, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
var n int
|
|
||||||
for {
|
|
||||||
if console.Buffered() > 0 {
|
|
||||||
data, _ := console.ReadByte()
|
|
||||||
switch data {
|
|
||||||
case 0x04:
|
|
||||||
fmt.Printf("wrote %d bytes to %s\r\n", n, tgt)
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
// anything else, just echo the character if it is printable
|
|
||||||
if strconv.IsPrint(rune(data)) {
|
|
||||||
console.WriteByte(data)
|
|
||||||
}
|
|
||||||
buf[0] = data
|
|
||||||
if _, err := f.Write(buf); err != nil {
|
|
||||||
fmt.Printf("\nerror writing: %s\r\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSampleFile(name string, buf []byte) (int, error) {
|
|
||||||
for j := uint8(0); j < uint8(len(buf)); j++ {
|
|
||||||
buf[j] = 0x20 + j
|
|
||||||
}
|
|
||||||
f, err := fs.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("error opening %s: %s", name, err.Error())
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
bytes, err := f.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("error writing %s: %s", name, err.Error())
|
|
||||||
}
|
|
||||||
return bytes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func cd(argv []string) {
|
|
||||||
|
|
||||||
if fatfs == nil || rootdir == nil {
|
|
||||||
mnt(nil)
|
|
||||||
}
|
|
||||||
if len(argv) == 1 {
|
|
||||||
currdir = rootdir
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tgt := ""
|
|
||||||
if len(argv) == 2 {
|
|
||||||
tgt = strings.TrimSpace(argv[1])
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Trying to cd to " + tgt)
|
|
||||||
}
|
|
||||||
if tgt == "" {
|
|
||||||
println("Usage: cd <target dir>")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Getting entry")
|
|
||||||
}
|
|
||||||
entry := currdir.Entry(tgt)
|
|
||||||
if entry == nil {
|
|
||||||
println("File not found: " + tgt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !entry.IsDir() {
|
|
||||||
println("Not a directory: " + tgt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Getting dir")
|
|
||||||
}
|
|
||||||
cd, err := entry.Dir()
|
|
||||||
if err != nil {
|
|
||||||
println("Could not cd to " + tgt + ": " + err.Error())
|
|
||||||
}
|
|
||||||
currdir = cd
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func cat(argv []string) {
|
|
||||||
tgt := ""
|
|
||||||
if len(argv) == 2 {
|
|
||||||
tgt = strings.TrimSpace(argv[1])
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Trying to cat to " + tgt)
|
|
||||||
}
|
|
||||||
if tgt == "" {
|
|
||||||
println("Usage: cat <target file>")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
println("Getting entry")
|
|
||||||
}
|
|
||||||
f, err := fs.Open(tgt)
|
|
||||||
if err != nil {
|
|
||||||
println("Could not open: " + err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if f.IsDir() {
|
|
||||||
println("Not a file: " + tgt)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
off := 0x0
|
|
||||||
buf := make([]byte, 64)
|
|
||||||
for {
|
|
||||||
n, err := f.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
println("Error reading " + tgt + ": " + err.Error())
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
xxdfprint(os.Stdout, uint32(off), buf[:n])
|
|
||||||
off += n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func xxd(argv []string) {
|
|
||||||
var err error
|
|
||||||
var addr uint64 = 0x0
|
|
||||||
var size int = 64
|
|
||||||
switch len(argv) {
|
|
||||||
case 3:
|
|
||||||
if size, err = strconv.Atoi(argv[2]); err != nil {
|
|
||||||
println("Invalid size argument: " + err.Error() + "\r\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if size > 512 || size < 1 {
|
|
||||||
fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", 512)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 2:
|
|
||||||
/*
|
|
||||||
if argv[1][:2] != "0x" {
|
|
||||||
println("Invalid hex address (should start with 0x)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil {
|
|
||||||
println("Invalid address: " + err.Error() + "\r\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 1:
|
|
||||||
// no args supplied, so nothing to do here, just use the defaults
|
|
||||||
default:
|
|
||||||
println("usage: xxd <hex address, ex: 0xA0> <size of hexdump in bytes>\r\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf := make([]byte, size)
|
|
||||||
//bsz := uint64(flash.SectorSize)
|
|
||||||
//blockdev.ReadBlock(uint32(addr/bsz), uint32(addr%bsz), buf)
|
|
||||||
flashdev.ReadAt(buf, int64(addr))
|
|
||||||
xxdfprint(os.Stdout, uint32(addr), buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func xxdfprint(w io.Writer, offset uint32, b []byte) {
|
|
||||||
var l int
|
|
||||||
var buf16 = make([]byte, 16)
|
|
||||||
for i, c := 0, len(b); i < c; i += 16 {
|
|
||||||
l = i + 16
|
|
||||||
if l >= c {
|
|
||||||
l = c
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%08x: % x ", offset+uint32(i), b[i:l])
|
|
||||||
for j, n := 0, l-i; j < 16; j++ {
|
|
||||||
if j >= n || !strconv.IsPrint(rune(b[i+j])) || b[i+j] >= 0x80 {
|
|
||||||
buf16[j] = '.'
|
|
||||||
} else {
|
|
||||||
buf16[j] = b[i+j]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.Write(buf16)
|
|
||||||
println()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prompt() {
|
|
||||||
print("==> ")
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build feather_m4 || feather_m4_can || feather_nrf52840
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
spi = &machine.SPI0
|
|
||||||
sckPin = machine.SPI0_SCK_PIN
|
|
||||||
sdoPin = machine.SPI0_SDO_PIN
|
|
||||||
sdiPin = machine.SPI0_SDI_PIN
|
|
||||||
csPin = machine.D10
|
|
||||||
|
|
||||||
ledPin = machine.LED
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build grandcentral_m4
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
spi = &machine.SPI1
|
|
||||||
sckPin = machine.SDCARD_SCK_PIN
|
|
||||||
sdoPin = machine.SDCARD_SDO_PIN
|
|
||||||
sdiPin = machine.SDCARD_SDI_PIN
|
|
||||||
csPin = machine.SDCARD_CS_PIN
|
|
||||||
|
|
||||||
ledPin = machine.LED
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build atsamd21 && !p1am_100
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
spi = &machine.SPI0
|
|
||||||
sckPin = machine.SPI0_SCK_PIN
|
|
||||||
sdoPin = machine.SPI0_SDO_PIN
|
|
||||||
sdiPin = machine.SPI0_SDI_PIN
|
|
||||||
csPin = machine.D2
|
|
||||||
|
|
||||||
ledPin = machine.LED
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"machine"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/examples/sdcard/tinyfs/console"
|
|
||||||
"tinygo.org/x/drivers/sdcard"
|
|
||||||
"tinygo.org/x/tinyfs/fatfs"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
spi *machine.SPI
|
|
||||||
sckPin machine.Pin
|
|
||||||
sdoPin machine.Pin
|
|
||||||
sdiPin machine.Pin
|
|
||||||
csPin machine.Pin
|
|
||||||
ledPin machine.Pin
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
sd := sdcard.New(spi, sckPin, sdoPin, sdiPin, csPin)
|
|
||||||
err := sd.Configure()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("%s\r\n", err.Error())
|
|
||||||
for {
|
|
||||||
time.Sleep(time.Hour)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filesystem := fatfs.New(&sd)
|
|
||||||
|
|
||||||
// Configure FATFS with sector size (must match value in ff.h - use 512)
|
|
||||||
filesystem.Configure(&fatfs.Config{
|
|
||||||
SectorSize: 512,
|
|
||||||
})
|
|
||||||
|
|
||||||
console.RunFor(&sd, filesystem)
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build p1am_100
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
spi = &machine.SDCARD_SPI
|
|
||||||
sckPin = machine.SDCARD_SCK_PIN
|
|
||||||
sdoPin = machine.SDCARD_SDO_PIN
|
|
||||||
sdiPin = machine.SDCARD_SDI_PIN
|
|
||||||
csPin = machine.SDCARD_SS_PIN
|
|
||||||
|
|
||||||
ledPin = machine.LED
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build pygamer
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
spi = &machine.SPI0
|
|
||||||
sckPin = machine.SPI0_SCK_PIN
|
|
||||||
sdoPin = machine.SPI0_SDO_PIN
|
|
||||||
sdiPin = machine.SPI0_SDI_PIN
|
|
||||||
csPin = machine.D4
|
|
||||||
|
|
||||||
ledPin = machine.LED
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build pyportal
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
spi = &machine.SPI0
|
|
||||||
sckPin = machine.SPI0_SCK_PIN
|
|
||||||
sdoPin = machine.SPI0_SDO_PIN
|
|
||||||
sdiPin = machine.SPI0_SDI_PIN
|
|
||||||
csPin = machine.D32 // SD_CS
|
|
||||||
|
|
||||||
ledPin = machine.LED
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build thingplus_rp2040
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
spi = machine.SPI1
|
|
||||||
sckPin = machine.SPI1_SCK_PIN
|
|
||||||
sdoPin = machine.SPI1_SDO_PIN
|
|
||||||
sdiPin = machine.SPI1_SDI_PIN
|
|
||||||
csPin = machine.GPIO9
|
|
||||||
|
|
||||||
ledPin = machine.LED
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
//go:build wioterminal
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
spi = &machine.SPI2
|
|
||||||
sckPin = machine.SCK2
|
|
||||||
sdoPin = machine.SDO2
|
|
||||||
sdiPin = machine.SDI2
|
|
||||||
csPin = machine.SS2
|
|
||||||
|
|
||||||
ledPin = machine.LED
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//go:build macropad_rp2040
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sh1106"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
display = sh1106.NewSPI(machine.SPI1, machine.OLED_DC, machine.OLED_RST, machine.OLED_CS)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
machine.SPI1.Configure(machine.SPIConfig{
|
||||||
|
Frequency: 48000000,
|
||||||
|
})
|
||||||
|
display.Configure(sh1106.Config{
|
||||||
|
Width: 128,
|
||||||
|
Height: 64,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
display.ClearDisplay()
|
||||||
|
|
||||||
|
x := int16(0)
|
||||||
|
y := int16(0)
|
||||||
|
deltaX := int16(1)
|
||||||
|
deltaY := int16(1)
|
||||||
|
for {
|
||||||
|
pixel := display.GetPixel(x, y)
|
||||||
|
c := color.RGBA{255, 255, 255, 255}
|
||||||
|
if pixel {
|
||||||
|
c = color.RGBA{0, 0, 0, 255}
|
||||||
|
}
|
||||||
|
display.SetPixel(x, y, c)
|
||||||
|
display.Display()
|
||||||
|
|
||||||
|
x += deltaX
|
||||||
|
y += deltaY
|
||||||
|
|
||||||
|
if x == 0 || x == 127 {
|
||||||
|
deltaX = -deltaX
|
||||||
|
}
|
||||||
|
|
||||||
|
if y == 0 || y == 63 {
|
||||||
|
deltaY = -deltaY
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,7 @@ import (
|
|||||||
"machine"
|
"machine"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
"tinygo.org/x/drivers/lora"
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
"tinygo.org/x/drivers/sx126x"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,12 +22,11 @@ func main() {
|
|||||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
// Create the driver
|
// Create the driver
|
||||||
loraRadio = sx126x.New(machine.SPI3)
|
loraRadio = sx126x.New(spi)
|
||||||
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
// Create RF Switch
|
// Create radio controller for target
|
||||||
var radioSwitch rfswitch.CustomSwitch
|
loraRadio.SetRadioController(newRadioControl())
|
||||||
loraRadio.SetRfSwitch(radioSwitch)
|
|
||||||
|
|
||||||
state := loraRadio.DetectDevice()
|
state := loraRadio.DetectDevice()
|
||||||
if !state {
|
if !state {
|
||||||
@@ -36,18 +34,18 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare for Lora operation
|
// Prepare for Lora operation
|
||||||
loraConf := sx126x.LoraConfig{
|
loraConf := lora.Config{
|
||||||
Freq: FREQ,
|
Freq: lora.MHz_868_1,
|
||||||
Bw: sx126x.SX126X_LORA_BW_500_0,
|
Bw: lora.Bandwidth_125_0,
|
||||||
Sf: sx126x.SX126X_LORA_SF9,
|
Sf: lora.SpreadingFactor9,
|
||||||
Cr: sx126x.SX126X_LORA_CR_4_7,
|
Cr: lora.CodingRate4_7,
|
||||||
HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT,
|
HeaderType: lora.HeaderExplicit,
|
||||||
Preamble: 12,
|
Preamble: 12,
|
||||||
Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF,
|
Ldr: lora.LowDataRateOptimizeOff,
|
||||||
Iq: sx126x.SX126X_LORA_IQ_STANDARD,
|
Iq: lora.IQStandard,
|
||||||
Crc: sx126x.SX126X_LORA_CRC_ON,
|
Crc: lora.CRCOn,
|
||||||
SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD,
|
SyncWord: lora.SyncPrivate,
|
||||||
LoraTxPowerDBm: 14,
|
LoraTxPowerDBm: 20,
|
||||||
}
|
}
|
||||||
loraRadio.LoraConfig(loraConf)
|
loraRadio.LoraConfig(loraConf)
|
||||||
|
|
||||||
@@ -75,6 +73,5 @@ func main() {
|
|||||||
|
|
||||||
loraRadio.SetStandby()
|
loraRadio.SetStandby()
|
||||||
time.Sleep(60 * time.Second)
|
time.Sleep(60 * time.Second)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !stm32wlx
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
spi = machine.SPI0
|
||||||
|
nssPin, busyPin, dio1Pin = machine.GP17, machine.GP10, machine.GP11
|
||||||
|
rxPin, txLowPin, txHighPin = machine.GP13, machine.GP12, machine.GP12
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl(nssPin, busyPin, dio1Pin, rxPin, txLowPin, txHighPin)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build stm32wlx
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var spi = machine.SPI3
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl()
|
||||||
|
}
|
||||||
@@ -4,18 +4,13 @@ package main
|
|||||||
// module will be in RX mode between two transmissions
|
// module will be in RX mode between two transmissions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"device/stm32"
|
|
||||||
"machine"
|
"machine"
|
||||||
"runtime/interrupt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
|
"tinygo.org/x/drivers/lora"
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
"tinygo.org/x/drivers/sx126x"
|
||||||
)
|
)
|
||||||
|
|
||||||
const FREQ = 868100000
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||||
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||||
@@ -26,23 +21,19 @@ var (
|
|||||||
txmsg = []byte("Hello TinyGO")
|
txmsg = []byte("Hello TinyGO")
|
||||||
)
|
)
|
||||||
|
|
||||||
// radioIntHandler will take care of radio interrupts
|
|
||||||
func radioIntHandler(intr interrupt.Interrupt) {
|
|
||||||
loraRadio.HandleInterrupt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
println("\n# TinyGo Lora RX/TX test")
|
println("\n# TinyGo Lora RX/TX test")
|
||||||
println("# ----------------------")
|
println("# ----------------------")
|
||||||
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
// Create the driver
|
// Create the driver
|
||||||
loraRadio = sx126x.New(machine.SPI3)
|
loraRadio = sx126x.New(spi)
|
||||||
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
|
||||||
|
|
||||||
// Create RF Switch
|
// Create radio controller for target
|
||||||
var radioSwitch rfswitch.CustomSwitch
|
loraRadio.SetRadioController(newRadioControl())
|
||||||
loraRadio.SetRfSwitch(radioSwitch)
|
|
||||||
|
|
||||||
// Detect the device
|
// Detect the device
|
||||||
state := loraRadio.DetectDevice()
|
state := loraRadio.DetectDevice()
|
||||||
@@ -50,21 +41,17 @@ func main() {
|
|||||||
panic("sx126x not detected.")
|
panic("sx126x not detected.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add interrupt handler for Radio IRQs
|
loraConf := lora.Config{
|
||||||
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
|
Freq: lora.MHz_868_1,
|
||||||
intr.Enable()
|
Bw: lora.Bandwidth_125_0,
|
||||||
|
Sf: lora.SpreadingFactor9,
|
||||||
loraConf := sx126x.LoraConfig{
|
Cr: lora.CodingRate4_7,
|
||||||
Freq: FREQ,
|
HeaderType: lora.HeaderExplicit,
|
||||||
Bw: sx126x.SX126X_LORA_BW_500_0,
|
|
||||||
Sf: sx126x.SX126X_LORA_SF9,
|
|
||||||
Cr: sx126x.SX126X_LORA_CR_4_7,
|
|
||||||
HeaderType: sx126x.SX126X_LORA_HEADER_EXPLICIT,
|
|
||||||
Preamble: 12,
|
Preamble: 12,
|
||||||
Ldr: sx126x.SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF,
|
Ldr: lora.LowDataRateOptimizeOff,
|
||||||
Iq: sx126x.SX126X_LORA_IQ_STANDARD,
|
Iq: lora.IQStandard,
|
||||||
Crc: sx126x.SX126X_LORA_CRC_ON,
|
Crc: lora.CRCOn,
|
||||||
SyncWord: sx126x.SX126X_LORA_MAC_PRIVATE_SYNCWORD,
|
SyncWord: lora.SyncPrivate,
|
||||||
LoraTxPowerDBm: 20,
|
LoraTxPowerDBm: 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,23 +59,20 @@ func main() {
|
|||||||
|
|
||||||
var count uint
|
var count uint
|
||||||
for {
|
for {
|
||||||
tStart := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Blocking RX for LORA_DEFAULT_RXTIMEOUT_MS
|
|
||||||
println("Start Lora RX for 10 sec")
|
|
||||||
for int(time.Now().Sub(tStart).Seconds()) < 10 {
|
|
||||||
buf, err := loraRadio.LoraRx(LORA_DEFAULT_RXTIMEOUT_MS)
|
|
||||||
|
|
||||||
|
println("main: Receiving Lora for 10 seconds")
|
||||||
|
for time.Since(start) < 10*time.Second {
|
||||||
|
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("RX Error: ", err)
|
println("RX Error: ", err)
|
||||||
} else if buf != nil {
|
} else if buf != nil {
|
||||||
println("Packet Received: len=", len(buf), string(buf))
|
println("Packet Received: len=", len(buf), string(buf))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println("END Lora RX")
|
println("main: End Lora RX")
|
||||||
|
println("LORA TX size=", len(txmsg), " -> ", string(txmsg))
|
||||||
println("LORA TX size=", len(txmsg))
|
err := loraRadio.Tx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
||||||
err := loraRadio.LoraTx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("TX Error:", err)
|
println("TX Error:", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//go:build !stm32wlx
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
spi = machine.SPI1
|
||||||
|
nssPin, busyPin, dio1Pin = machine.GP13, machine.GP6, machine.GP7
|
||||||
|
rxPin, txLowPin, txHighPin = machine.GP9, machine.GP8, machine.GP8
|
||||||
|
)
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl(nssPin, busyPin, dio1Pin, rxPin, txLowPin, txHighPin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
spi.Configure(machine.SPIConfig{
|
||||||
|
Mode: 0,
|
||||||
|
Frequency: 8 * 1e6,
|
||||||
|
SDO: machine.SPI1_SDO_PIN,
|
||||||
|
SDI: machine.SPI1_SDI_PIN,
|
||||||
|
SCK: machine.SPI1_SCK_PIN,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
//go:build stm32wlx
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/sx126x"
|
||||||
|
)
|
||||||
|
|
||||||
|
var spi = machine.SPI3
|
||||||
|
|
||||||
|
func newRadioControl() sx126x.RadioController {
|
||||||
|
return sx126x.NewRadioControl()
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
//go:build gnse
|
|
||||||
|
|
||||||
/*
|
|
||||||
Generic Node Sensor Edition
|
|
||||||
RFSwitch
|
|
||||||
|
|
||||||
Disable Switch : PB8=OFF PA0=OFF PA1=OFF
|
|
||||||
Enable RX : PB8=ON PA0=ON PA1=OFF
|
|
||||||
Enable TX RFO LP : PB8=ON PA0=ON PA1=ON
|
|
||||||
Enable TX RFO HP : PB8=ON PA0=OFF PA1=ON
|
|
||||||
*/
|
|
||||||
package rfswitch
|
|
||||||
|
|
||||||
import (
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CustomSwitch struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
rfstate int
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s CustomSwitch) InitRFSwitch() {
|
|
||||||
machine.RF_FE_CTRL1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.RF_FE_CTRL2.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.RF_FE_CTRL3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
rfstate = -1 //Unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
|
||||||
if mode == rfstate {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch mode {
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_TX_HP:
|
|
||||||
machine.RF_FE_CTRL1.Set(false)
|
|
||||||
machine.RF_FE_CTRL2.Set(true)
|
|
||||||
machine.RF_FE_CTRL3.Set(true)
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_TX_LP:
|
|
||||||
machine.RF_FE_CTRL1.Set(true)
|
|
||||||
machine.RF_FE_CTRL2.Set(true)
|
|
||||||
machine.RF_FE_CTRL3.Set(true)
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_RX:
|
|
||||||
machine.RF_FE_CTRL1.Set(true)
|
|
||||||
machine.RF_FE_CTRL2.Set(false)
|
|
||||||
machine.RF_FE_CTRL3.Set(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
rfstate = mode
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
//go:build lorae5
|
|
||||||
|
|
||||||
package radio
|
|
||||||
|
|
||||||
/*
|
|
||||||
/!\ LoRa-E5 module ONLY transmits through RFO_HP:
|
|
||||||
|
|
||||||
Receive: PA4=1, PA5=0
|
|
||||||
Transmit(high output power, SMPS mode): PA4=0, PA5=1
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"machine"
|
|
||||||
|
|
||||||
"tinygo.org/x/drivers/sx126x"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CustomSwitch struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s CustomSwitch) InitRFSwitch() {
|
|
||||||
machine.PA4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
machine.PB5.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
|
|
||||||
switch mode {
|
|
||||||
|
|
||||||
case sx126x.RFSWITCH_RX:
|
|
||||||
machine.PA4.Set(true)
|
|
||||||
machine.PB5.Set(false)
|
|
||||||
case sx126x.RFSWITCH_TX_LP:
|
|
||||||
errors.New("RFSWITCH_TX_LP not supported ")
|
|
||||||
case sx126x.RFSWITCH_TX_HP:
|
|
||||||
machine.PA4.Set(false)
|
|
||||||
machine.PB5.Set(true)
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// This example code demonstrates Lora RX/TX With SX127x driver
|
||||||
|
// You need to connect SPI, RST, CS, DIO0 (aka IRQ) and DIO1 to use.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/lora"
|
||||||
|
"tinygo.org/x/drivers/sx127x"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LORA_DEFAULT_RXTIMEOUT_MS = 1000
|
||||||
|
LORA_DEFAULT_TXTIMEOUT_MS = 5000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loraRadio *sx127x.Device
|
||||||
|
txmsg = []byte("Hello TinyGO")
|
||||||
|
|
||||||
|
// We assume LoRa Featherwing module is connected to PyBadge:
|
||||||
|
SX127X_PIN_RST = machine.D11
|
||||||
|
SX127X_PIN_CS = machine.D10
|
||||||
|
SX127X_PIN_DIO0 = machine.D6
|
||||||
|
SX127X_PIN_DIO1 = machine.D9
|
||||||
|
SX127X_SPI = machine.SPI0
|
||||||
|
)
|
||||||
|
|
||||||
|
func dioIrqHandler(machine.Pin) {
|
||||||
|
loraRadio.HandleInterrupt()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
println("\n# TinyGo Lora RX/TX test")
|
||||||
|
println("# ----------------------")
|
||||||
|
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
SX127X_PIN_RST.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||||
|
|
||||||
|
SX127X_SPI.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
|
||||||
|
|
||||||
|
println("main: create and start SX127x driver")
|
||||||
|
loraRadio = sx127x.New(SX127X_SPI, SX127X_PIN_RST)
|
||||||
|
loraRadio.SetRadioController(sx127x.NewRadioControl(SX127X_PIN_CS, SX127X_PIN_DIO0, SX127X_PIN_DIO1))
|
||||||
|
|
||||||
|
loraRadio.Reset()
|
||||||
|
state := loraRadio.DetectDevice()
|
||||||
|
if !state {
|
||||||
|
panic("main: sx127x NOT FOUND !!!")
|
||||||
|
} else {
|
||||||
|
println("main: sx127x found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare for Lora Operation
|
||||||
|
loraConf := lora.Config{
|
||||||
|
Freq: lora.MHz_868_1,
|
||||||
|
Bw: lora.Bandwidth_125_0,
|
||||||
|
Sf: lora.SpreadingFactor9,
|
||||||
|
Cr: lora.CodingRate4_7,
|
||||||
|
HeaderType: lora.HeaderExplicit,
|
||||||
|
Preamble: 12,
|
||||||
|
Iq: lora.IQStandard,
|
||||||
|
Crc: lora.CRCOn,
|
||||||
|
SyncWord: lora.SyncPrivate,
|
||||||
|
LoraTxPowerDBm: 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
loraRadio.LoraConfig(loraConf)
|
||||||
|
|
||||||
|
var count uint
|
||||||
|
for {
|
||||||
|
tStart := time.Now()
|
||||||
|
|
||||||
|
println("main: Receiving Lora for 10 seconds")
|
||||||
|
for time.Since(tStart) < 10*time.Second {
|
||||||
|
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
|
||||||
|
if err != nil {
|
||||||
|
println("RX Error: ", err)
|
||||||
|
} else if buf != nil {
|
||||||
|
println("Packet Received: len=", len(buf), string(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("main: End Lora RX")
|
||||||
|
println("LORA TX size=", len(txmsg), " -> ", string(txmsg))
|
||||||
|
err := loraRadio.Tx(txmsg, LORA_DEFAULT_TXTIMEOUT_MS)
|
||||||
|
if err != nil {
|
||||||
|
println("TX Error:", err)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/ttp229"
|
||||||
|
|
||||||
|
"machine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
sensor := ttp229.NewPin(machine.A5, machine.A4)
|
||||||
|
sensor.Configure(ttp229.Configuration{Inputs: 16})
|
||||||
|
|
||||||
|
println("READY")
|
||||||
|
for {
|
||||||
|
sensor.ReadKeys()
|
||||||
|
for i := byte(0); i < 16; i++ {
|
||||||
|
if sensor.IsKeyPressed(i) {
|
||||||
|
print("1 ")
|
||||||
|
} else {
|
||||||
|
print("0 ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("")
|
||||||
|
|
||||||
|
println("Pressed key:", sensor.GetKey())
|
||||||
|
|
||||||
|
for i := byte(0); i < 16; i++ {
|
||||||
|
if sensor.IsKeyDown(i) {
|
||||||
|
println("Key", i, "is down")
|
||||||
|
} else if sensor.IsKeyUp(i) {
|
||||||
|
println("Key", i, "is up")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,17 +128,29 @@ func waitSerial() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func failMessage(msg string) {
|
||||||
|
for {
|
||||||
|
println(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ func main() {
|
|||||||
waitSerial()
|
waitSerial()
|
||||||
|
|
||||||
connectToAP()
|
connectToAP()
|
||||||
|
displayIP()
|
||||||
|
|
||||||
// You can send and receive cookies in the following way
|
// You can send and receive cookies in the following way
|
||||||
// import "tinygo.org/x/drivers/net/http/cookiejar"
|
// import "tinygo.org/x/drivers/net/http/cookiejar"
|
||||||
@@ -131,28 +132,42 @@ func waitSerial() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayIP() {
|
||||||
ip, _, _, err := adaptor.GetIP()
|
ip, _, _, err := adaptor.GetIP()
|
||||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
message(err.Error())
|
message(err.Error())
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
message(ip.String())
|
message("IP address: " + ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func message(msg string) {
|
func message(msg string) {
|
||||||
println(msg, "\r")
|
println(msg, "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func failMessage(msg string) {
|
||||||
|
for {
|
||||||
|
println(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ func main() {
|
|||||||
adaptor.Configure()
|
adaptor.Configure()
|
||||||
|
|
||||||
connectToAP()
|
connectToAP()
|
||||||
|
displayIP()
|
||||||
|
|
||||||
opts := mqtt.NewClientOptions()
|
opts := mqtt.NewClientOptions()
|
||||||
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
|
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
|
||||||
@@ -101,26 +102,33 @@ func main() {
|
|||||||
println("Done.")
|
println("Done.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayIP() {
|
||||||
ip, _, _, err := adaptor.GetIP()
|
ip, _, _, err := adaptor.GetIP()
|
||||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
println(err.Error())
|
println(err.Error())
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
println(ip.String())
|
println("IP address: " + ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an int >= min, < max
|
// Returns an int >= min, < max
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ func main() {
|
|||||||
adaptor.Configure()
|
adaptor.Configure()
|
||||||
|
|
||||||
connectToAP()
|
connectToAP()
|
||||||
|
displayIP()
|
||||||
|
|
||||||
opts := mqtt.NewClientOptions()
|
opts := mqtt.NewClientOptions()
|
||||||
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
|
opts.AddBroker(server).SetClientID("tinygo-client-" + randomString(10))
|
||||||
@@ -113,27 +114,33 @@ func publishing() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
func displayIP() {
|
||||||
ip, _, _, err := adaptor.GetIP()
|
ip, _, _, err := adaptor.GetIP()
|
||||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
println(err.Error())
|
println(err.Error())
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
println(ip.String())
|
println("IP address: " + ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an int >= min, < max
|
// Returns an int >= min, < max
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ func main() {
|
|||||||
waitSerial()
|
waitSerial()
|
||||||
|
|
||||||
connectToAP()
|
connectToAP()
|
||||||
|
displayIP()
|
||||||
|
|
||||||
// now make UDP connection
|
// now make UDP connection
|
||||||
ip := net.ParseIP(ntpHost)
|
ip := net.ParseIP(ntpHost)
|
||||||
@@ -149,29 +150,42 @@ func clearBuffer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
func displayIP() {
|
||||||
ip, _, _, err := adaptor.GetIP()
|
ip, _, _, err := adaptor.GetIP()
|
||||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
message(err.Error())
|
message(err.Error())
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
message(ip.String())
|
message("IP address: " + ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func message(format string, args ...interface{}) {
|
func message(format string, args ...interface{}) {
|
||||||
println(fmt.Sprintf(format, args...), "\r")
|
println(fmt.Sprintf(format, args...), "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func failMessage(msg string) {
|
||||||
|
for {
|
||||||
|
println(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func main() {
|
|||||||
adaptor.Configure()
|
adaptor.Configure()
|
||||||
|
|
||||||
connectToAP()
|
connectToAP()
|
||||||
|
displayIP()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
sendBatch()
|
sendBatch()
|
||||||
@@ -111,29 +112,42 @@ func sendBatch() {
|
|||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
func displayIP() {
|
||||||
ip, _, _, err := adaptor.GetIP()
|
ip, _, _, err := adaptor.GetIP()
|
||||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
message(err.Error())
|
message(err.Error())
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
message(ip.String())
|
message("IP address: " + ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func message(msg string) {
|
func message(msg string) {
|
||||||
println(msg, "\r")
|
println(msg, "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func failMessage(msg string) {
|
||||||
|
for {
|
||||||
|
println(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ func main() {
|
|||||||
waitSerial()
|
waitSerial()
|
||||||
|
|
||||||
connectToAP()
|
connectToAP()
|
||||||
|
displayIP()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
readConnection()
|
readConnection()
|
||||||
@@ -121,29 +122,42 @@ func makeHTTPSRequest() {
|
|||||||
lastRequestTime = time.Now()
|
lastRequestTime = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
func displayIP() {
|
||||||
ip, _, _, err := adaptor.GetIP()
|
ip, _, _, err := adaptor.GetIP()
|
||||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
message(err.Error())
|
message(err.Error())
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
message(ip.String())
|
message("IP address: " + ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func message(msg string) {
|
func message(msg string) {
|
||||||
println(msg, "\r")
|
println(msg, "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func failMessage(msg string) {
|
||||||
|
for {
|
||||||
|
println(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ func main() {
|
|||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
connectToAP()
|
connectToAP()
|
||||||
|
displayIP()
|
||||||
|
|
||||||
// now make UDP connection
|
// now make UDP connection
|
||||||
ip := net.ParseIP(hubIP)
|
ip := net.ParseIP(hubIP)
|
||||||
@@ -57,7 +58,10 @@ func main() {
|
|||||||
laddr := &net.UDPAddr{Port: 2222}
|
laddr := &net.UDPAddr{Port: 2222}
|
||||||
|
|
||||||
println("Dialing UDP connection...")
|
println("Dialing UDP connection...")
|
||||||
conn, _ := net.DialUDP("udp", laddr, raddr)
|
conn, err := net.DialUDP("udp", laddr, raddr)
|
||||||
|
if err != nil {
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// send data
|
// send data
|
||||||
@@ -74,28 +78,42 @@ func main() {
|
|||||||
println("Done.")
|
println("Done.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayIP() {
|
||||||
ip, _, _, err := adaptor.GetIP()
|
ip, _, _, err := adaptor.GetIP()
|
||||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
message(err.Error())
|
message(err.Error())
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
message(ip.String())
|
message("IP address: " + ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func message(msg string) {
|
func message(msg string) {
|
||||||
println(msg, "\r")
|
println(msg, "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func failMessage(msg string) {
|
||||||
|
for {
|
||||||
|
println(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ var (
|
|||||||
pass string
|
pass string
|
||||||
)
|
)
|
||||||
|
|
||||||
// IP address of the server aka "hub". Replace with your own info.
|
// IP address of the "example.com" server. Replace with your own info.
|
||||||
const server = "tinygo.org"
|
const server = "93.184.216.34"
|
||||||
|
|
||||||
// these are the default pins for the Arduino Nano33 IoT.
|
// these are the default pins for the Arduino Nano33 IoT.
|
||||||
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
// change these to connect to a different UART or pins for the ESP8266/ESP32
|
||||||
@@ -63,6 +63,7 @@ func main() {
|
|||||||
waitSerial()
|
waitSerial()
|
||||||
|
|
||||||
connectToAP()
|
connectToAP()
|
||||||
|
displayIP()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
readConnection()
|
readConnection()
|
||||||
@@ -123,28 +124,42 @@ func makeHTTPRequest() {
|
|||||||
lastRequestTime = time.Now()
|
lastRequestTime = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
// connect to access point
|
// connect to access point
|
||||||
func connectToAP() {
|
func connectToAP() {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
println("Connecting to " + ssid)
|
var err error
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
if err != nil { // error connecting to AP
|
println("Connecting to " + ssid)
|
||||||
for {
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
println(err)
|
if err == nil {
|
||||||
time.Sleep(1 * time.Second)
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Connected.")
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayIP() {
|
||||||
ip, _, _, err := adaptor.GetIP()
|
ip, _, _, err := adaptor.GetIP()
|
||||||
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
message(err.Error())
|
message(err.Error())
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
message(ip.String())
|
message("IP address: " + ip.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func message(msg string) {
|
func message(msg string) {
|
||||||
println(msg, "\r")
|
println(msg, "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func failMessage(msg string) {
|
||||||
|
for {
|
||||||
|
println(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ var (
|
|||||||
pass string
|
pass string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// this is the ESP chip that has the WIFININA firmware flashed on it
|
||||||
|
adaptor *wifinina.Device
|
||||||
|
)
|
||||||
|
|
||||||
var led = machine.LED
|
var led = machine.LED
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -49,31 +54,15 @@ func run() error {
|
|||||||
SCK: machine.NINA_SCK,
|
SCK: machine.NINA_SCK,
|
||||||
})
|
})
|
||||||
|
|
||||||
adaptor := wifinina.New(spi,
|
adaptor = wifinina.New(spi,
|
||||||
machine.NINA_CS,
|
machine.NINA_CS,
|
||||||
machine.NINA_ACK,
|
machine.NINA_ACK,
|
||||||
machine.NINA_GPIO0,
|
machine.NINA_GPIO0,
|
||||||
machine.NINA_RESETN)
|
machine.NINA_RESETN)
|
||||||
adaptor.Configure()
|
adaptor.Configure()
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
connectToAP()
|
||||||
println("Connecting to " + ssid)
|
displayIP()
|
||||||
err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Connected.")
|
|
||||||
|
|
||||||
time.Sleep(2 * time.Second)
|
|
||||||
ip, subnet, gateway, err := adaptor.GetIP()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("IP Address : %s\r\n", ip)
|
|
||||||
fmt.Printf("Mask : %s\r\n", subnet)
|
|
||||||
fmt.Printf("Gateway : %s\r\n", gateway)
|
|
||||||
|
|
||||||
http.UseDriver(adaptor)
|
http.UseDriver(adaptor)
|
||||||
|
|
||||||
@@ -204,6 +193,42 @@ func cnt(w http.ResponseWriter, r *http.Request) {
|
|||||||
fmt.Fprintf(w, `{"cnt": %d}`, counter)
|
fmt.Fprintf(w, `{"cnt": %d}`, counter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const retriesBeforeFailure = 3
|
||||||
|
|
||||||
|
// connect to access point
|
||||||
|
func connectToAP() {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
var err error
|
||||||
|
for i := 0; i < retriesBeforeFailure; i++ {
|
||||||
|
println("Connecting to " + ssid)
|
||||||
|
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
|
||||||
|
if err == nil {
|
||||||
|
println("Connected.")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// error connecting to AP
|
||||||
|
failMessage(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func displayIP() {
|
||||||
|
ip, _, _, err := adaptor.GetIP()
|
||||||
|
for ; err != nil; ip, _, _, err = adaptor.GetIP() {
|
||||||
|
message(err.Error())
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
message("IP address: " + ip.String())
|
||||||
|
}
|
||||||
|
|
||||||
func message(msg string) {
|
func message(msg string) {
|
||||||
println(msg, "\r")
|
println(msg, "\r")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func failMessage(msg string) {
|
||||||
|
for {
|
||||||
|
println(msg)
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ go 1.15
|
|||||||
require (
|
require (
|
||||||
github.com/eclipse/paho.mqtt.golang v1.2.0
|
github.com/eclipse/paho.mqtt.golang v1.2.0
|
||||||
github.com/frankban/quicktest v1.10.2
|
github.com/frankban/quicktest v1.10.2
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
|
||||||
tinygo.org/x/tinyfont v0.3.0
|
tinygo.org/x/tinyfont v0.3.0
|
||||||
tinygo.org/x/tinyfs v0.2.0
|
|
||||||
tinygo.org/x/tinyterm v0.1.0
|
tinygo.org/x/tinyterm v0.1.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d8
|
|||||||
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
|
||||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as=
|
github.com/hajimehoshi/go-jisx0208 v1.0.0/go.mod h1:yYxEStHL7lt9uL+AbdWgW9gBumwieDoZCiB1f/0X0as=
|
||||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
@@ -32,7 +34,5 @@ tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKO
|
|||||||
tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM=
|
tinygo.org/x/tinyfont v0.3.0 h1:HIRLQoI3oc+2CMhPcfv+Ig88EcTImE/5npjqOnMD4lM=
|
||||||
tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk=
|
tinygo.org/x/tinyfont v0.3.0/go.mod h1:+TV5q0KpwSGRWnN+ITijsIhrWYJkoUCp9MYELjKpAXk=
|
||||||
tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20=
|
tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20=
|
||||||
tinygo.org/x/tinyfs v0.2.0 h1:M0lwZC/dEGFt16XYN5GTQsif/qCkAN2qUVNxELVD1xg=
|
|
||||||
tinygo.org/x/tinyfs v0.2.0/go.mod h1:6ZHYdvB3sFYeMB3ypmXZCNEnFwceKc61ADYTYHpep1E=
|
|
||||||
tinygo.org/x/tinyterm v0.1.0 h1:80i+j+KWoxCFa/Xfp6pWbh79x+8zUdMXC1vaKj2QhkY=
|
tinygo.org/x/tinyterm v0.1.0 h1:80i+j+KWoxCFa/Xfp6pWbh79x+8zUdMXC1vaKj2QhkY=
|
||||||
tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4=
|
tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4=
|
||||||
|
|||||||
+46
-6
@@ -13,6 +13,39 @@ import (
|
|||||||
var (
|
var (
|
||||||
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
errInvalidNMEASentenceLength = errors.New("invalid NMEA sentence length")
|
||||||
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
errInvalidNMEAChecksum = errors.New("invalid NMEA sentence checksum")
|
||||||
|
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
|
||||||
|
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
|
||||||
|
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
|
||||||
|
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
|
||||||
|
errInvalidGLLSentence = errors.New("invalid GLL NMEA sentence")
|
||||||
|
)
|
||||||
|
|
||||||
|
type GPSError struct {
|
||||||
|
Err error
|
||||||
|
Info string
|
||||||
|
Sentence string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGPSError(err error, sentence string, info string) GPSError {
|
||||||
|
return GPSError{
|
||||||
|
Info: info,
|
||||||
|
Err: err,
|
||||||
|
Sentence: sentence,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ge GPSError) Error() string {
|
||||||
|
return ge.Err.Error() + " " + ge.Info + " " + ge.Sentence
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ge GPSError) Unwrap() error {
|
||||||
|
return ge.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
minimumNMEALength = 7
|
||||||
|
startingDelimiter = '$'
|
||||||
|
checksumDelimiter = '*'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Device wraps a connection to a GPS device.
|
// Device wraps a connection to a GPS device.
|
||||||
@@ -60,11 +93,11 @@ func (gps *Device) readNextSentence() (sentence string) {
|
|||||||
gps.sentence.Reset()
|
gps.sentence.Reset()
|
||||||
var b byte = ' '
|
var b byte = ' '
|
||||||
|
|
||||||
for b != '$' {
|
for b != startingDelimiter {
|
||||||
b = gps.readNextByte()
|
b = gps.readNextByte()
|
||||||
}
|
}
|
||||||
|
|
||||||
for b != '*' {
|
for b != checksumDelimiter {
|
||||||
gps.sentence.WriteByte(b)
|
gps.sentence.WriteByte(b)
|
||||||
b = gps.readNextByte()
|
b = gps.readNextByte()
|
||||||
}
|
}
|
||||||
@@ -126,17 +159,24 @@ func (gps *Device) WriteBytes(bytes []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validSentence checks if a sentence has been received uncorrupted
|
// validSentence checks if a sentence has been received uncorrupted
|
||||||
|
// For example, a valid NMEA sentence such as this:
|
||||||
|
// $GPGLL,3751.65,S,14507.36,E*77
|
||||||
|
// It has to start with a '$' character.
|
||||||
|
// It has to have a 5 character long sentence identifier.
|
||||||
|
// It has to end with a '*' character following by a checksum.
|
||||||
func validSentence(sentence string) error {
|
func validSentence(sentence string) error {
|
||||||
if len(sentence) < 4 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
|
if len(sentence) < minimumNMEALength || sentence[0] != startingDelimiter || sentence[len(sentence)-3] != checksumDelimiter {
|
||||||
return errInvalidNMEASentenceLength
|
return errInvalidNMEASentenceLength
|
||||||
}
|
}
|
||||||
var cs byte = 0
|
var cs byte = 0
|
||||||
for i := 1; i < len(sentence)-3; i++ {
|
for i := 1; i < len(sentence)-3; i++ {
|
||||||
cs ^= sentence[i]
|
cs ^= sentence[i]
|
||||||
}
|
}
|
||||||
checksum := hex.EncodeToString([]byte{cs})
|
checksum := strings.ToUpper(hex.EncodeToString([]byte{cs}))
|
||||||
if (checksum[0] != sentence[len(sentence)-2]) || (checksum[1] != sentence[len(sentence)-1]) {
|
if checksum != sentence[len(sentence)-2:len(sentence)] {
|
||||||
return errInvalidNMEAChecksum
|
return newGPSError(errInvalidNMEAChecksum, sentence,
|
||||||
|
"expected "+sentence[len(sentence)-2:len(sentence)]+
|
||||||
|
" got "+checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+63
-32
@@ -1,19 +1,11 @@
|
|||||||
package gps
|
package gps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
errEmptyNMEASentence = errors.New("cannot parse empty NMEA sentence")
|
|
||||||
errUnknownNMEASentence = errors.New("unsupported NMEA sentence type")
|
|
||||||
errInvalidGGASentence = errors.New("invalid GGA NMEA sentence")
|
|
||||||
errInvalidRMCSentence = errors.New("invalid RMC NMEA sentence")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parser for GPS NMEA sentences.
|
// Parser for GPS NMEA sentences.
|
||||||
type Parser struct {
|
type Parser struct {
|
||||||
}
|
}
|
||||||
@@ -51,43 +43,65 @@ func NewParser() Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses a NMEA sentence looking for fix info.
|
// Parse parses a NMEA sentence looking for fix info.
|
||||||
func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
|
func (parser *Parser) Parse(sentence string) (Fix, error) {
|
||||||
|
var fix Fix
|
||||||
if sentence == "" {
|
if sentence == "" {
|
||||||
err = errEmptyNMEASentence
|
return fix, errEmptyNMEASentence
|
||||||
return
|
}
|
||||||
|
if len(sentence) < 6 {
|
||||||
|
return fix, errInvalidNMEASentenceLength
|
||||||
}
|
}
|
||||||
typ := sentence[3:6]
|
typ := sentence[3:6]
|
||||||
switch typ {
|
switch typ {
|
||||||
case "GGA":
|
case "GGA":
|
||||||
|
// https://docs.novatel.com/OEM7/Content/Logs/GPGGA.htm
|
||||||
fields := strings.Split(sentence, ",")
|
fields := strings.Split(sentence, ",")
|
||||||
if len(fields) != 15 {
|
if len(fields) != 15 {
|
||||||
err = errInvalidGGASentence
|
return fix, errInvalidGGASentence
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fix.Altitude = findAltitude(fields[9])
|
|
||||||
fix.Satellites = findSatellites(fields[7])
|
|
||||||
fix.Longitude = findLongitude(fields[4], fields[5])
|
|
||||||
fix.Latitude = findLatitude(fields[2], fields[3])
|
|
||||||
fix.Time = findTime(fields[1])
|
fix.Time = findTime(fields[1])
|
||||||
|
fix.Latitude = findLatitude(fields[2], fields[3])
|
||||||
|
fix.Longitude = findLongitude(fields[4], fields[5])
|
||||||
|
fix.Satellites = findSatellites(fields[7])
|
||||||
|
fix.Altitude = findAltitude(fields[9])
|
||||||
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
fix.Valid = (fix.Altitude != -99999) && (fix.Satellites > 0)
|
||||||
|
|
||||||
|
return fix, nil
|
||||||
|
case "GLL":
|
||||||
|
// https://docs.novatel.com/OEM7/Content/Logs/GPGLL.htm
|
||||||
|
fields := strings.Split(sentence, ",")
|
||||||
|
if len(fields) != 8 {
|
||||||
|
return fix, errInvalidGLLSentence
|
||||||
|
}
|
||||||
|
|
||||||
|
fix.Latitude = findLatitude(fields[1], fields[2])
|
||||||
|
fix.Longitude = findLongitude(fields[3], fields[4])
|
||||||
|
fix.Time = findTime(fields[5])
|
||||||
|
|
||||||
|
fix.Valid = (fields[6] == "A")
|
||||||
|
|
||||||
|
return fix, nil
|
||||||
case "RMC":
|
case "RMC":
|
||||||
|
// https://docs.novatel.com/OEM7/Content/Logs/GPRMC.htm
|
||||||
fields := strings.Split(sentence, ",")
|
fields := strings.Split(sentence, ",")
|
||||||
if len(fields) != 13 {
|
if len(fields) != 13 {
|
||||||
err = errInvalidRMCSentence
|
return fix, errInvalidRMCSentence
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fix.Longitude = findLongitude(fields[5], fields[6])
|
|
||||||
fix.Latitude = findLatitude(fields[3], fields[4])
|
|
||||||
fix.Time = findTime(fields[1])
|
fix.Time = findTime(fields[1])
|
||||||
|
fix.Valid = (fields[2] == "A")
|
||||||
|
fix.Latitude = findLatitude(fields[3], fields[4])
|
||||||
|
fix.Longitude = findLongitude(fields[5], fields[6])
|
||||||
fix.Speed = findSpeed(fields[7])
|
fix.Speed = findSpeed(fields[7])
|
||||||
fix.Heading = findHeading(fields[8])
|
fix.Heading = findHeading(fields[8])
|
||||||
fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
|
date := findDate(fields[9])
|
||||||
default:
|
fix.Time = fix.Time.AddDate(date.Year(), int(date.Month()), date.Day())
|
||||||
err = errUnknownNMEASentence
|
|
||||||
|
return fix, nil
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
return fix, newGPSError(errUnknownNMEASentence, sentence, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findTime returns the time from an NMEA sentence:
|
// findTime returns the time from an NMEA sentence:
|
||||||
@@ -100,7 +114,10 @@ func findTime(val string) time.Time {
|
|||||||
h, _ := strconv.ParseInt(val[0:2], 10, 8)
|
h, _ := strconv.ParseInt(val[0:2], 10, 8)
|
||||||
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
||||||
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
s, _ := strconv.ParseInt(val[4:6], 10, 8)
|
||||||
ms, _ := strconv.ParseInt(val[7:10], 10, 16)
|
ms := int64(0)
|
||||||
|
if len(val) > 6 {
|
||||||
|
ms, _ = strconv.ParseInt(val[7:], 10, 16)
|
||||||
|
}
|
||||||
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
t := time.Date(0, 0, 0, int(h), int(m), int(s), int(ms), time.UTC)
|
||||||
|
|
||||||
return t
|
return t
|
||||||
@@ -120,11 +137,11 @@ func findAltitude(val string) int32 {
|
|||||||
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
|
// $--GGA,,ddmm.mmmmm,x,,,,,,,,,,,*hh
|
||||||
func findLatitude(val, hemi string) float32 {
|
func findLatitude(val, hemi string) float32 {
|
||||||
if len(val) > 8 {
|
if len(val) > 8 {
|
||||||
var dd = val[0:2]
|
dd := val[0:2]
|
||||||
var mm = val[2:]
|
mm := val[2:]
|
||||||
var d, _ = strconv.ParseFloat(dd, 32)
|
d, _ := strconv.ParseFloat(dd, 32)
|
||||||
var m, _ = strconv.ParseFloat(mm, 32)
|
m, _ := strconv.ParseFloat(mm, 32)
|
||||||
var v = float32(d + (m / 60))
|
v := float32(d + (m / 60))
|
||||||
if hemi == "S" {
|
if hemi == "S" {
|
||||||
v *= -1
|
v *= -1
|
||||||
}
|
}
|
||||||
@@ -133,7 +150,7 @@ func findLatitude(val, hemi string) float32 {
|
|||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// findLatitude returns the longitude from an NMEA sentence:
|
// findLongitude returns the longitude from an NMEA sentence:
|
||||||
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
|
// $--GGA,,,,dddmm.mmmmm,x,,,,,,,,,*hh
|
||||||
func findLongitude(val, hemi string) float32 {
|
func findLongitude(val, hemi string) float32 {
|
||||||
if len(val) > 8 {
|
if len(val) > 8 {
|
||||||
@@ -162,6 +179,20 @@ func findSatellites(val string) (n int16) {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// findDate returns the date from an RMC NMEA sentence.
|
||||||
|
func findDate(val string) time.Time {
|
||||||
|
if len(val) < 6 {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
d, _ := strconv.ParseInt(val[0:2], 10, 8)
|
||||||
|
m, _ := strconv.ParseInt(val[2:4], 10, 8)
|
||||||
|
y, _ := strconv.ParseInt(val[4:6], 10, 8)
|
||||||
|
t := time.Date(int(2000+y), time.Month(m), int(d), 0, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
// findSpeed returns the speed from an RMC NMEA sentence.
|
// findSpeed returns the speed from an RMC NMEA sentence.
|
||||||
func findSpeed(val string) float32 {
|
func findSpeed(val string) float32 {
|
||||||
if len(val) > 0 {
|
if len(val) > 0 {
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package gps
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseUnknownSentence(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
p := NewParser()
|
||||||
|
|
||||||
|
val := "$GPGSV,3,1,09,07,14,317,22,08,31,284,25,10,32,133,39,16,85,232,29*7F"
|
||||||
|
_, err := p.Parse(val)
|
||||||
|
c.Assert(err.Error(), qt.Contains, "unsupported NMEA sentence type")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGGA(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
p := NewParser()
|
||||||
|
|
||||||
|
val := "$GPGGA,115739.00,4158.8441367,N,09147.4416929,"
|
||||||
|
fix, err := p.Parse(val)
|
||||||
|
if err != errInvalidGGASentence {
|
||||||
|
t.Error("should have errInvalidGGASentence error")
|
||||||
|
}
|
||||||
|
|
||||||
|
val = "$GPGGA,115739.00,4158.8441367,N,09147.4416929,W,4,13,0.9,255.747,M,-32.00,M,01,0000*6E"
|
||||||
|
fix, err = p.Parse(val)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should have parsed")
|
||||||
|
}
|
||||||
|
c.Assert(fix.Latitude, qt.Equals, float32(41.980735778808594))
|
||||||
|
c.Assert(fix.Longitude, qt.Equals, float32(-91.79069519042969))
|
||||||
|
c.Assert(fix.Altitude, qt.Equals, int32(255))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseGLL(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
p := NewParser()
|
||||||
|
|
||||||
|
val := "$GPGLL,3953.88008971,N,10506.7531891"
|
||||||
|
_, err := p.Parse(val)
|
||||||
|
if err != errInvalidGLLSentence {
|
||||||
|
t.Error("should have errInvalidGLLSentence error")
|
||||||
|
}
|
||||||
|
|
||||||
|
val = "$GPGLL,5109.0262317,N,11401.8407304,W,202725.00,A,D*79"
|
||||||
|
fix, err := p.Parse(val)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should have parsed")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(fix.Latitude, qt.Equals, float32(51.15043640136719))
|
||||||
|
c.Assert(fix.Longitude, qt.Equals, float32(-114.03067779541016))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseRMC(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
p := NewParser()
|
||||||
|
|
||||||
|
val := "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,"
|
||||||
|
_, err := p.Parse(val)
|
||||||
|
if err != errInvalidRMCSentence {
|
||||||
|
t.Error("should have errInvalidRMCSentence error")
|
||||||
|
}
|
||||||
|
|
||||||
|
val = "$GPRMC,203522.00,A,5109.0262308,N,11401.8407342,W,0.004,133.4,130522,0.0,E,D*2B"
|
||||||
|
fix, err := p.Parse(val)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("should have parsed")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Assert(fix.Time.Year(), qt.Equals, 2022)
|
||||||
|
c.Assert(fix.Time.Month(), qt.Equals, time.May)
|
||||||
|
c.Assert(fix.Time.Day(), qt.Equals, 13)
|
||||||
|
c.Assert(fix.Time.Hour(), qt.Equals, 20)
|
||||||
|
c.Assert(fix.Time.Minute(), qt.Equals, 35)
|
||||||
|
c.Assert(fix.Time.Second(), qt.Equals, 22)
|
||||||
|
c.Assert(fix.Latitude, qt.Equals, float32(51.15043640136719))
|
||||||
|
c.Assert(fix.Longitude, qt.Equals, float32(-114.03067779541016))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTime(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
val := ""
|
||||||
|
tm := findTime(val)
|
||||||
|
c.Assert(tm, qt.Equals, time.Time{})
|
||||||
|
|
||||||
|
val = "225446"
|
||||||
|
tm = findTime(val)
|
||||||
|
c.Assert(tm, qt.Equals, time.Date(0, 0, 0, 22, 54, 46, 0, time.UTC))
|
||||||
|
|
||||||
|
val = "124326.02752"
|
||||||
|
tm = findTime(val)
|
||||||
|
c.Assert(tm, qt.Equals, time.Date(0, 0, 0, 12, 43, 26, 2752, time.UTC))
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ package i2csoft
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"machine"
|
"machine"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/delay"
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C is an I2C implementation by Software. Since it is implemented by
|
// I2C is an I2C implementation by Software. Since it is implemented by
|
||||||
@@ -278,3 +281,8 @@ func (i2c *I2C) WriteRegister(address uint8, register uint8, data []byte) error
|
|||||||
func (i2c *I2C) ReadRegister(address uint8, register uint8, data []byte) error {
|
func (i2c *I2C) ReadRegister(address uint8, register uint8, data []byte) error {
|
||||||
return i2c.Tx(uint16(address), []byte{register}, data)
|
return i2c.Tx(uint16(address), []byte{register}, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait waits for half the time of the SCL operation interval.
|
||||||
|
func (i2c *I2C) wait() {
|
||||||
|
delay.Sleep(50 * time.Microsecond) // half of a 100kHz cycle (50µs)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
//go:build atsamd51 || atsame5x
|
|
||||||
|
|
||||||
package i2csoft
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait waits for half the time of the SCL operation interval. It is set to
|
|
||||||
// about 100 kHz.
|
|
||||||
func (i2c *I2C) wait() {
|
|
||||||
wait := 20
|
|
||||||
for i := 0; i < wait; i++ {
|
|
||||||
device.Asm(`nop`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//go:build esp32
|
|
||||||
|
|
||||||
package i2csoft
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait waits for half the time of the SCL operation interval. It is set to
|
|
||||||
// about 100 kHz.
|
|
||||||
func (i2c *I2C) wait() {
|
|
||||||
wait := 60
|
|
||||||
for i := 0; i < wait; i++ {
|
|
||||||
device.Asm(`nop`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//go:build nrf52840
|
|
||||||
|
|
||||||
package i2csoft
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait waits for half the time of the SCL operation interval. It is set to
|
|
||||||
// about 100 kHz.
|
|
||||||
func (i2c *I2C) wait() {
|
|
||||||
wait := 26
|
|
||||||
for i := 0; i < wait; i++ {
|
|
||||||
device.Asm(`nop`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build !esp32 && !atsamd51 && !atsame5x && !stm32f4 && !rp2040 && !nrf52840
|
|
||||||
|
|
||||||
package i2csoft
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait waits for half the time of the SCL operation interval.
|
|
||||||
func (i2c *I2C) wait() {
|
|
||||||
wait := 20
|
|
||||||
for i := 0; i < wait; i++ {
|
|
||||||
device.Asm(`nop`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//go:build rp2040
|
|
||||||
|
|
||||||
package i2csoft
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait waits for half the time of the SCL operation interval. It is set to
|
|
||||||
// about 100 kHz.
|
|
||||||
func (i2c *I2C) wait() {
|
|
||||||
wait := 50
|
|
||||||
for i := 0; i < wait; i++ {
|
|
||||||
device.Asm(`nop`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//go:build stm32f4
|
|
||||||
|
|
||||||
package i2csoft
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device"
|
|
||||||
)
|
|
||||||
|
|
||||||
// wait waits for half the time of the SCL operation interval. It is set to
|
|
||||||
// about 100 kHz.
|
|
||||||
func (i2c *I2C) wait() {
|
|
||||||
wait := 77
|
|
||||||
for i := 0; i < wait; i++ {
|
|
||||||
device.Asm(`nop`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+58
-13
@@ -5,19 +5,21 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
"machine"
|
"machine"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Width int16
|
Width int16
|
||||||
Height int16
|
Height int16
|
||||||
Rotation Rotation
|
Rotation drivers.Rotation
|
||||||
DisplayInversion bool
|
DisplayInversion bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
width int16
|
width int16
|
||||||
height int16
|
height int16
|
||||||
rotation Rotation
|
rotation drivers.Rotation
|
||||||
driver driver
|
driver driver
|
||||||
|
|
||||||
x0, x1 int16 // cached address window; prevents useless/expensive
|
x0, x1 int16 // cached address window; prevents useless/expensive
|
||||||
@@ -136,10 +138,12 @@ func (d *Device) Configure(config Config) {
|
|||||||
|
|
||||||
// Size returns the current size of the display.
|
// Size returns the current size of the display.
|
||||||
func (d *Device) Size() (x, y int16) {
|
func (d *Device) Size() (x, y int16) {
|
||||||
if d.rotation == 1 || d.rotation == 3 {
|
switch d.rotation {
|
||||||
|
case Rotation90, Rotation270, Rotation90Mirror, Rotation270Mirror:
|
||||||
return d.height, d.width
|
return d.height, d.width
|
||||||
|
default: // Rotation0, Rotation180, etc
|
||||||
|
return d.width, d.height
|
||||||
}
|
}
|
||||||
return d.width, d.height
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPixel modifies the internal buffer.
|
// SetPixel modifies the internal buffer.
|
||||||
@@ -156,6 +160,18 @@ func (d *Device) Display() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnableTEOutput enables the TE ("tearing effect") line.
|
||||||
|
// The TE line goes high when the screen is not currently being updated and can
|
||||||
|
// be used to start drawing. When used correctly, it can avoid tearing entirely.
|
||||||
|
func (d *Device) EnableTEOutput(on bool) {
|
||||||
|
if on {
|
||||||
|
cmdBuf[0] = 0
|
||||||
|
d.sendCommand(TEON, cmdBuf[:1]) // M=0 (V-blanking only, no H-blanking)
|
||||||
|
} else {
|
||||||
|
d.sendCommand(TEOFF, nil) // TEOFF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates
|
// DrawRGBBitmap copies an RGB bitmap to the internal buffer at given coordinates
|
||||||
func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
|
func (d *Device) DrawRGBBitmap(x, y int16, data []uint16, w, h int16) error {
|
||||||
k, i := d.Size()
|
k, i := d.Size()
|
||||||
@@ -241,13 +257,41 @@ func (d *Device) FillScreen(c color.RGBA) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRotation returns the current rotation of the device
|
// Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot
|
||||||
func (d *Device) GetRotation() Rotation {
|
// less power. The LCD won't display an image anymore, but the memory contents
|
||||||
|
// will be kept.
|
||||||
|
func (d *Device) Sleep(sleepEnabled bool) error {
|
||||||
|
if sleepEnabled {
|
||||||
|
// Shut down LCD panel.
|
||||||
|
d.sendCommand(SLPIN, nil)
|
||||||
|
time.Sleep(5 * time.Millisecond) // 5ms required by the datasheet
|
||||||
|
} else {
|
||||||
|
// Turn the LCD panel back on.
|
||||||
|
d.sendCommand(SLPOUT, nil)
|
||||||
|
// Note: the ili9341 documentation says that it is needed to wait at
|
||||||
|
// least 120ms before going to sleep again. Sleeping here would not be
|
||||||
|
// practical (delays turning on the screen too much), so just hope the
|
||||||
|
// screen won't need to sleep again for at least 120ms.
|
||||||
|
// In practice, it's unlikely the user will set the display to sleep
|
||||||
|
// again within 120ms.
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotation returns the current rotation of the device.
|
||||||
|
func (d *Device) Rotation() drivers.Rotation {
|
||||||
return d.rotation
|
return d.rotation
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetRotation changes the rotation of the device (clock-wise)
|
// GetRotation returns the current rotation of the device.
|
||||||
func (d *Device) SetRotation(rotation Rotation) {
|
//
|
||||||
|
// Deprecated: use Rotation instead.
|
||||||
|
func (d *Device) GetRotation() drivers.Rotation {
|
||||||
|
return d.rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRotation changes the rotation of the device (clock-wise).
|
||||||
|
func (d *Device) SetRotation(rotation drivers.Rotation) error {
|
||||||
madctl := uint8(0)
|
madctl := uint8(0)
|
||||||
switch rotation % 8 {
|
switch rotation % 8 {
|
||||||
case Rotation0:
|
case Rotation0:
|
||||||
@@ -255,21 +299,22 @@ func (d *Device) SetRotation(rotation Rotation) {
|
|||||||
case Rotation90:
|
case Rotation90:
|
||||||
madctl = MADCTL_MV | MADCTL_BGR
|
madctl = MADCTL_MV | MADCTL_BGR
|
||||||
case Rotation180:
|
case Rotation180:
|
||||||
madctl = MADCTL_MY | MADCTL_BGR
|
madctl = MADCTL_MY | MADCTL_BGR | MADCTL_ML
|
||||||
case Rotation270:
|
case Rotation270:
|
||||||
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR
|
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML
|
||||||
case Rotation0Mirror:
|
case Rotation0Mirror:
|
||||||
madctl = MADCTL_BGR
|
madctl = MADCTL_BGR
|
||||||
case Rotation90Mirror:
|
case Rotation90Mirror:
|
||||||
madctl = MADCTL_MY | MADCTL_MV | MADCTL_BGR
|
madctl = MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML
|
||||||
case Rotation180Mirror:
|
case Rotation180Mirror:
|
||||||
madctl = MADCTL_MX | MADCTL_MY | MADCTL_BGR
|
madctl = MADCTL_MX | MADCTL_MY | MADCTL_BGR | MADCTL_ML
|
||||||
case Rotation270Mirror:
|
case Rotation270Mirror:
|
||||||
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR
|
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML
|
||||||
}
|
}
|
||||||
cmdBuf[0] = madctl
|
cmdBuf[0] = madctl
|
||||||
d.sendCommand(MADCTL, cmdBuf[:1])
|
d.sendCommand(MADCTL, cmdBuf[:1])
|
||||||
d.rotation = rotation
|
d.rotation = rotation
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetScrollArea sets an area to scroll with fixed top/bottom or left/right parts of the display
|
// SetScrollArea sets an area to scroll with fixed top/bottom or left/right parts of the display
|
||||||
|
|||||||
+12
-8
@@ -1,5 +1,7 @@
|
|||||||
package ili9341
|
package ili9341
|
||||||
|
|
||||||
|
import "tinygo.org/x/drivers"
|
||||||
|
|
||||||
type Rotation uint8
|
type Rotation uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -39,6 +41,8 @@ const (
|
|||||||
|
|
||||||
PTLAR = 0x30 ///< Partial Area
|
PTLAR = 0x30 ///< Partial Area
|
||||||
VSCRDEF = 0x33 ///< Vertical Scrolling Definition
|
VSCRDEF = 0x33 ///< Vertical Scrolling Definition
|
||||||
|
TEOFF = 0x34 ///< TEOFF: Tearing Effect Line OFF
|
||||||
|
TEON = 0x35 ///< TEON: Tearing Effect Line ON
|
||||||
MADCTL = 0x36 ///< Memory Access Control
|
MADCTL = 0x36 ///< Memory Access Control
|
||||||
VSCRSADD = 0x37 ///< Vertical Scrolling Start Address
|
VSCRSADD = 0x37 ///< Vertical Scrolling Start Address
|
||||||
PIXFMT = 0x3A ///< COLMOD: Pixel Format Set
|
PIXFMT = 0x3A ///< COLMOD: Pixel Format Set
|
||||||
@@ -77,13 +81,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Rotation0 Rotation = 0
|
Rotation0 = drivers.Rotation0
|
||||||
Rotation90 Rotation = 1 // 90 degrees clock-wise rotation
|
Rotation90 = drivers.Rotation90 // 90 degrees clock-wise rotation
|
||||||
Rotation180 Rotation = 2
|
Rotation180 = drivers.Rotation180
|
||||||
Rotation270 Rotation = 3
|
Rotation270 = drivers.Rotation270
|
||||||
|
|
||||||
Rotation0Mirror Rotation = 4
|
Rotation0Mirror = drivers.Rotation0Mirror
|
||||||
Rotation90Mirror Rotation = 5
|
Rotation90Mirror = drivers.Rotation90Mirror
|
||||||
Rotation180Mirror Rotation = 6
|
Rotation180Mirror = drivers.Rotation180Mirror
|
||||||
Rotation270Mirror Rotation = 7
|
Rotation270Mirror = drivers.Rotation270Mirror
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package lora
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Config holds the LoRa configuration parameters
|
||||||
|
type Config struct {
|
||||||
|
Freq uint32 // Frequency
|
||||||
|
Cr uint8 // Coding Rate
|
||||||
|
Sf uint8 // Spread Factor
|
||||||
|
Bw uint8 // Bandwidth
|
||||||
|
Ldr uint8 // Low Data Rate
|
||||||
|
Preamble uint16 // PreambleLength
|
||||||
|
SyncWord uint16 // Sync Word
|
||||||
|
HeaderType uint8 // Header : Implicit/explicit
|
||||||
|
Crc uint8 // CRC : Yes/No
|
||||||
|
Iq uint8 // iq : Standard/inverted
|
||||||
|
LoraTxPowerDBm int8 // Tx power in Dbm
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUndefinedLoraConf = errors.New("Undefined Lora configuration")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SpreadingFactor5 = 0x05
|
||||||
|
SpreadingFactor6 = 0x06
|
||||||
|
SpreadingFactor7 = 0x07
|
||||||
|
SpreadingFactor8 = 0x08
|
||||||
|
SpreadingFactor9 = 0x09
|
||||||
|
SpreadingFactor10 = 0x0A
|
||||||
|
SpreadingFactor11 = 0x0B
|
||||||
|
SpreadingFactor12 = 0x0C
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CodingRate4_5 = 0x01 // 7 0 LoRa coding rate: 4/5
|
||||||
|
CodingRate4_6 = 0x02 // 7 0 4/6
|
||||||
|
CodingRate4_7 = 0x03 // 7 0 4/7
|
||||||
|
CodingRate4_8 = 0x04 // 7 0 4/8
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HeaderExplicit = 0x00 // 7 0 LoRa header mode: explicit
|
||||||
|
HeaderImplicit = 0x01 // 7 0 implicit
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LowDataRateOptimizeOff = 0x00 // 7 0 LoRa low data rate optimization: disabled
|
||||||
|
LowDataRateOptimizeOn = 0x01 // 7 0 enabled
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CRCOff = 0x00 // 7 0 LoRa CRC mode: disabled
|
||||||
|
CRCOn = 0x01 // 7 0 enabled
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
IQStandard = 0x00 // 7 0 LoRa IQ setup: standard
|
||||||
|
IQInverted = 0x01 // 7 0 inverted
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Bandwidth_7_8 = iota // 7.8 kHz
|
||||||
|
Bandwidth_10_4 // 10.4 kHz
|
||||||
|
Bandwidth_15_6 // 15.6 kHz
|
||||||
|
Bandwidth_20_8 // 20.8 kHz
|
||||||
|
Bandwidth_31_25 // 31.25 kHz
|
||||||
|
Bandwidth_41_7 // 41.7 kHz
|
||||||
|
Bandwidth_62_5 // 62.5 kHz
|
||||||
|
Bandwidth_125_0 // 125.0 kHz
|
||||||
|
Bandwidth_250_0 // 250.0 kHz
|
||||||
|
Bandwidth_500_0 // 500.0 kHz
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SyncPublic = iota
|
||||||
|
SyncPrivate
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MHz_868_1 = 868100000
|
||||||
|
MHz_868_5 = 868500000
|
||||||
|
MHz_916_8 = 916800000
|
||||||
|
MHz_923_3 = 923300000
|
||||||
|
)
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers/lora"
|
||||||
|
"tinygo.org/x/drivers/lora/lorawan/region"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoJoinAcceptReceived = errors.New("no JoinAccept packet received")
|
||||||
|
ErrNoRadioAttached = errors.New("no LoRa radio attached")
|
||||||
|
ErrInvalidEuiLength = errors.New("invalid EUI length")
|
||||||
|
ErrInvalidAppKeyLength = errors.New("invalid AppKey length")
|
||||||
|
ErrInvalidPacketLength = errors.New("invalid packet length")
|
||||||
|
ErrInvalidDevAddrLength = errors.New("invalid DevAddr length")
|
||||||
|
ErrInvalidMic = errors.New("invalid Mic")
|
||||||
|
ErrFrmPayloadTooLarge = errors.New("FRM payload too large")
|
||||||
|
ErrInvalidNetIDLength = errors.New("invalid NetID length")
|
||||||
|
ErrInvalidNwkSKeyLength = errors.New("invalid NwkSKey length")
|
||||||
|
ErrInvalidAppSKeyLength = errors.New("invalid AppSKey length")
|
||||||
|
ErrUndefinedRegionSettings = errors.New("undefined Regionnal Settings ")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LORA_TX_TIMEOUT = 2000
|
||||||
|
LORA_RX_TIMEOUT = 10000
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ActiveRadio lora.Radio
|
||||||
|
Retries = 15
|
||||||
|
regionSettings region.RegionSettings
|
||||||
|
)
|
||||||
|
|
||||||
|
// UseRegionSettings sets current Lorawan Regional parameters
|
||||||
|
func UseRegionSettings(rs region.RegionSettings) {
|
||||||
|
regionSettings = rs
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseRadio attaches Lora radio driver to Lorawan
|
||||||
|
func UseRadio(r lora.Radio) {
|
||||||
|
if ActiveRadio != nil {
|
||||||
|
panic("lorawan.ActiveRadio is already set")
|
||||||
|
}
|
||||||
|
ActiveRadio = r
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPublicNetwork defines Lora Sync Word according to network type (public/private)
|
||||||
|
func SetPublicNetwork(enabled bool) {
|
||||||
|
ActiveRadio.SetPublicNetwork(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyChannelConfig sets current Lora modulation according to current regional settings
|
||||||
|
func applyChannelConfig(ch *region.Channel) {
|
||||||
|
ActiveRadio.SetFrequency(ch.Frequency)
|
||||||
|
ActiveRadio.SetBandwidth(ch.Bandwidth)
|
||||||
|
ActiveRadio.SetCodingRate(ch.CodingRate)
|
||||||
|
ActiveRadio.SetSpreadingFactor(ch.SpreadingFactor)
|
||||||
|
ActiveRadio.SetPreambleLength(ch.PreambleLength)
|
||||||
|
ActiveRadio.SetTxPower(ch.TxPowerDBm)
|
||||||
|
// Lorawan defaults to explicit headers
|
||||||
|
ActiveRadio.SetHeaderType(lora.HeaderExplicit)
|
||||||
|
ActiveRadio.SetCrc(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join tries to connect Lorawan Gateway
|
||||||
|
func Join(otaa *Otaa, session *Session) error {
|
||||||
|
var resp []uint8
|
||||||
|
|
||||||
|
if ActiveRadio == nil {
|
||||||
|
return ErrNoRadioAttached
|
||||||
|
}
|
||||||
|
|
||||||
|
if regionSettings == nil {
|
||||||
|
return ErrUndefinedRegionSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
otaa.Init()
|
||||||
|
|
||||||
|
// Send join packet
|
||||||
|
payload, err := otaa.GenerateJoinRequest()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare radio for Join Tx
|
||||||
|
applyChannelConfig(regionSettings.JoinRequestChannel())
|
||||||
|
ActiveRadio.SetIqMode(lora.IQStandard)
|
||||||
|
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for JoinAccept
|
||||||
|
applyChannelConfig(regionSettings.JoinAcceptChannel())
|
||||||
|
ActiveRadio.SetIqMode(lora.IQInverted)
|
||||||
|
resp, err = ActiveRadio.Rx(LORA_RX_TIMEOUT)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
return ErrNoJoinAcceptReceived
|
||||||
|
}
|
||||||
|
|
||||||
|
err = otaa.DecodeJoinAccept(resp, session)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendUplink sends Lorawan Uplink message
|
||||||
|
func SendUplink(data []uint8, session *Session) error {
|
||||||
|
|
||||||
|
if regionSettings == nil {
|
||||||
|
return ErrUndefinedRegionSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
payload, err := session.GenMessage(0, []byte(data))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
applyChannelConfig(regionSettings.UplinkChannel())
|
||||||
|
ActiveRadio.SetIqMode(lora.IQStandard)
|
||||||
|
ActiveRadio.Tx(payload, LORA_TX_TIMEOUT)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListenDownlink() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import "crypto/rand"
|
||||||
|
|
||||||
|
// reverseBytes reverses order of a given byte slice
|
||||||
|
func reverseBytes(s []byte) []byte {
|
||||||
|
result := make([]byte, len(s))
|
||||||
|
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
result[i], result[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRand16 returns 2 random bytes
|
||||||
|
func GetRand16() ([2]uint8, error) {
|
||||||
|
var randomBytes [2]byte
|
||||||
|
_, err := rand.Read(randomBytes[:])
|
||||||
|
|
||||||
|
return randomBytes, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"hash"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cmacHash struct {
|
||||||
|
ciph cipher.Block
|
||||||
|
k1 []byte
|
||||||
|
k2 []byte
|
||||||
|
data []byte
|
||||||
|
x []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
Size = aes.BlockSize
|
||||||
|
blockSize = Size
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
subkeyZero []byte
|
||||||
|
subkeyRb []byte
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
subkeyZero = bytes.Repeat([]byte{0x00}, blockSize)
|
||||||
|
subkeyRb = append(bytes.Repeat([]byte{0x00}, blockSize-1), 0x87)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns an AES-CMAC hash using the supplied key. The key must be 16, 24,
|
||||||
|
// or 32 bytes long.
|
||||||
|
func NewCmac(key []byte) (hash.Hash, error) {
|
||||||
|
// Create a cipher.
|
||||||
|
ciph, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Set up the hash object.
|
||||||
|
h := &cmacHash{ciph: ciph}
|
||||||
|
h.k1, h.k2 = generateSubkeys(ciph)
|
||||||
|
h.Reset()
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Xor(dst []byte, a []byte, b []byte) error {
|
||||||
|
if len(dst) != len(a) || len(a) != len(b) {
|
||||||
|
panic("crypto/Xor: bad length")
|
||||||
|
}
|
||||||
|
for i, _ := range a {
|
||||||
|
dst[i] = a[i] ^ b[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) Reset() {
|
||||||
|
h.data = h.data[:0]
|
||||||
|
h.x = make([]byte, blockSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) BlockSize() int {
|
||||||
|
return h.ciph.BlockSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) Size() int {
|
||||||
|
return h.ciph.BlockSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) Sum(b []byte) []byte {
|
||||||
|
dataLen := len(h.data)
|
||||||
|
|
||||||
|
// We should have at most one block left.
|
||||||
|
if dataLen > blockSize {
|
||||||
|
panic("cmacHash err1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate M_last.
|
||||||
|
mLast := make([]byte, blockSize)
|
||||||
|
if dataLen == blockSize {
|
||||||
|
Xor(mLast, h.data, h.k1)
|
||||||
|
} else {
|
||||||
|
// TODO(jacobsa): Accept a destination buffer in common.PadBlock and
|
||||||
|
// simplify this code.
|
||||||
|
Xor(mLast, PadBlock(h.data), h.k2)
|
||||||
|
}
|
||||||
|
|
||||||
|
y := make([]byte, blockSize)
|
||||||
|
Xor(y, mLast, h.x)
|
||||||
|
|
||||||
|
result := make([]byte, blockSize)
|
||||||
|
h.ciph.Encrypt(result, y)
|
||||||
|
|
||||||
|
b = append(b, result...)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) Write(p []byte) (n int, err error) {
|
||||||
|
n = len(p)
|
||||||
|
|
||||||
|
// First step: consume enough data to expand h.data to a full block, if
|
||||||
|
// possible.
|
||||||
|
{
|
||||||
|
toConsume := blockSize - len(h.data)
|
||||||
|
if toConsume > len(p) {
|
||||||
|
toConsume = len(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.data = append(h.data, p[:toConsume]...)
|
||||||
|
p = p[toConsume:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's no data left in p, it means h.data might not be a full block.
|
||||||
|
// Even if it is, we're not sure it's the final block, which we must treat
|
||||||
|
// specially. So we must stop here.
|
||||||
|
if len(p) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// h.data is a full block and is not the last; process it.
|
||||||
|
h.writeBlocks(h.data)
|
||||||
|
h.data = h.data[:0]
|
||||||
|
|
||||||
|
// Consume any further full blocks in p that we're sure aren't the last. Note
|
||||||
|
// that we're sure that len(p) is greater than zero here.
|
||||||
|
blocksToProcess := (len(p) - 1) / blockSize
|
||||||
|
bytesToProcess := blocksToProcess * blockSize
|
||||||
|
|
||||||
|
h.writeBlocks(p[:bytesToProcess])
|
||||||
|
p = p[bytesToProcess:]
|
||||||
|
|
||||||
|
// Store the rest for later.
|
||||||
|
h.data = append(h.data, p...)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *cmacHash) writeBlocks(p []byte) {
|
||||||
|
y := make([]byte, blockSize)
|
||||||
|
|
||||||
|
for off := 0; off < len(p); off += blockSize {
|
||||||
|
block := p[off : off+blockSize]
|
||||||
|
|
||||||
|
xorBlock(
|
||||||
|
unsafe.Pointer(&y[0]),
|
||||||
|
unsafe.Pointer(&h.x[0]),
|
||||||
|
unsafe.Pointer(&block[0]))
|
||||||
|
|
||||||
|
h.ciph.Encrypt(h.x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func xorBlock(
|
||||||
|
dstPtr unsafe.Pointer,
|
||||||
|
aPtr unsafe.Pointer,
|
||||||
|
bPtr unsafe.Pointer) {
|
||||||
|
// Check assumptions. (These are compile-time constants, so this should
|
||||||
|
// compile out.)
|
||||||
|
const wordSize = unsafe.Sizeof(uintptr(0))
|
||||||
|
if blockSize != 4*wordSize {
|
||||||
|
panic("xorBlock err1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert.
|
||||||
|
a := (*[4]uintptr)(aPtr)
|
||||||
|
b := (*[4]uintptr)(bPtr)
|
||||||
|
dst := (*[4]uintptr)(dstPtr)
|
||||||
|
|
||||||
|
// Compute.
|
||||||
|
dst[0] = a[0] ^ b[0]
|
||||||
|
dst[1] = a[1] ^ b[1]
|
||||||
|
dst[2] = a[2] ^ b[2]
|
||||||
|
dst[3] = a[3] ^ b[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
func PadBlock(block []byte) []byte {
|
||||||
|
blockLen := len(block)
|
||||||
|
if blockLen >= aes.BlockSize {
|
||||||
|
panic("PadBlock input must be less than 16 bytes.")
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]byte, aes.BlockSize)
|
||||||
|
copy(result, block)
|
||||||
|
result[blockLen] = 0x80
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given the supplied cipher, whose block size must be 16 bytes, return two
|
||||||
|
// subkeys that can be used in MAC generation. See section 5.3 of NIST SP
|
||||||
|
// 800-38B. Note that the other NIST-approved block size of 8 bytes is not
|
||||||
|
// supported by this function.
|
||||||
|
func generateSubkeys(ciph cipher.Block) (k1 []byte, k2 []byte) {
|
||||||
|
if ciph.BlockSize() != blockSize {
|
||||||
|
panic("generateSubkeys requires a cipher with a block size of 16 bytes.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1
|
||||||
|
l := make([]byte, blockSize)
|
||||||
|
ciph.Encrypt(l, subkeyZero)
|
||||||
|
|
||||||
|
// Step 2: Derive the first subkey.
|
||||||
|
if Msb(l) == 0 {
|
||||||
|
// TODO(jacobsa): Accept a destination buffer in ShiftLeft and then hoist
|
||||||
|
// the allocation in the else branch below.
|
||||||
|
k1 = ShiftLeft(l)
|
||||||
|
} else {
|
||||||
|
k1 = make([]byte, blockSize)
|
||||||
|
Xor(k1, ShiftLeft(l), subkeyRb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Derive the second subkey.
|
||||||
|
if Msb(k1) == 0 {
|
||||||
|
k2 = ShiftLeft(k1)
|
||||||
|
} else {
|
||||||
|
k2 = make([]byte, blockSize)
|
||||||
|
Xor(k2, ShiftLeft(k1), subkeyRb)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShiftLeft(b []byte) []byte {
|
||||||
|
l := len(b)
|
||||||
|
if l == 0 {
|
||||||
|
panic("shiftLeft requires a non-empty buffer.")
|
||||||
|
}
|
||||||
|
|
||||||
|
output := make([]byte, l)
|
||||||
|
|
||||||
|
overflow := byte(0)
|
||||||
|
for i := int(l - 1); i >= 0; i-- {
|
||||||
|
output[i] = b[i] << 1
|
||||||
|
output[i] |= overflow
|
||||||
|
overflow = (b[i] & 0x80) >> 7
|
||||||
|
}
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
// Msb returns the most significant bit of the supplied data (which must be
|
||||||
|
// non-empty). This is the MSB(L) function of RFC 4493.
|
||||||
|
func Msb(buf []byte) uint8 {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
panic("msb requires non-empty buffer.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf[0] >> 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
// genPayloadMIC computes MIC given the payload and the key
|
||||||
|
func genPayloadMIC(payload []uint8, key [16]uint8) [4]uint8 {
|
||||||
|
var mic [4]uint8
|
||||||
|
hash, _ := NewCmac(key[:])
|
||||||
|
hash.Write(payload)
|
||||||
|
hb := hash.Sum([]byte{})
|
||||||
|
copy(mic[:], hb[0:4])
|
||||||
|
return mic
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcMessageMIC(payload []uint8, key [16]uint8, dir uint8, addr []byte, fCnt uint32, lenMessage uint8) [4]uint8 {
|
||||||
|
var b0 []byte
|
||||||
|
b0 = append(b0, 0x49, 0x00, 0x00, 0x00, 0x00)
|
||||||
|
b0 = append(b0, dir)
|
||||||
|
b0 = append(b0, addr[:]...)
|
||||||
|
var b [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(b[:], fCnt)
|
||||||
|
b0 = append(b0, b[:]...)
|
||||||
|
b0 = append(b0, 0x00)
|
||||||
|
b0 = append(b0, lenMessage)
|
||||||
|
|
||||||
|
var full []byte
|
||||||
|
full = append(full, b0...)
|
||||||
|
full = append(full, payload...)
|
||||||
|
|
||||||
|
var mic [4]uint8
|
||||||
|
hash, _ := NewCmac(key[:])
|
||||||
|
hash.Write(full)
|
||||||
|
hb := hash.Sum([]byte{})
|
||||||
|
copy(mic[:], hb[0:4])
|
||||||
|
return mic
|
||||||
|
}
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Otaa is used to store Over The Air Activation data of a LoRaWAN session
|
||||||
|
type Otaa struct {
|
||||||
|
DevEUI [8]uint8
|
||||||
|
AppEUI [8]uint8
|
||||||
|
AppKey [16]uint8
|
||||||
|
devNonce [2]uint8
|
||||||
|
appNonce [3]uint8
|
||||||
|
NetID [3]uint8
|
||||||
|
buf []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize DevNonce
|
||||||
|
func (o *Otaa) Init() {
|
||||||
|
o.buf = make([]uint8, 0)
|
||||||
|
o.generateDevNonce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) generateDevNonce() {
|
||||||
|
// TODO: handle error
|
||||||
|
rnd, _ := GetRand16()
|
||||||
|
o.devNonce[0] = rnd[0]
|
||||||
|
o.devNonce[1] = rnd[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) incrementDevNonce() {
|
||||||
|
nonce := uint16(o.devNonce[1])<<8 | uint16(o.devNonce[0]) + 1
|
||||||
|
o.devNonce[0] = uint8(nonce)
|
||||||
|
o.devNonce[1] = uint8((nonce >> 8))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set configures the Otaa AppEUI, DevEUI, AppKey for the device
|
||||||
|
func (o *Otaa) Set(appEUI []uint8, devEUI []uint8, appKey []uint8) {
|
||||||
|
o.SetAppEUI(appEUI)
|
||||||
|
o.SetDevEUI(devEUI)
|
||||||
|
o.SetAppKey(appKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAppEUI configures the Otaa AppEUI
|
||||||
|
func (o *Otaa) SetAppEUI(appEUI []uint8) error {
|
||||||
|
if len(appEUI) != 8 {
|
||||||
|
return ErrInvalidEuiLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(o.AppEUI[:], appEUI)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) GetAppEUI() string {
|
||||||
|
return hex.EncodeToString(o.AppEUI[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDevEUI configures the Otaa DevEUI
|
||||||
|
func (o *Otaa) SetDevEUI(devEUI []uint8) error {
|
||||||
|
if len(devEUI) != 8 {
|
||||||
|
return ErrInvalidEuiLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(o.DevEUI[:], devEUI)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) GetDevEUI() string {
|
||||||
|
return hex.EncodeToString(o.DevEUI[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAppKey configures the Otaa AppKey
|
||||||
|
func (o *Otaa) SetAppKey(appKey []uint8) error {
|
||||||
|
if len(appKey) != 16 {
|
||||||
|
return ErrInvalidAppKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(o.AppKey[:], appKey)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) GetAppKey() string {
|
||||||
|
return hex.EncodeToString(o.AppKey[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) GetNetID() string {
|
||||||
|
return hex.EncodeToString(o.NetID[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Otaa) SetNetID(netID []uint8) error {
|
||||||
|
if len(netID) != 3 {
|
||||||
|
return ErrInvalidNetIDLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(o.NetID[:], netID)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateJoinRequest Generates a LoraWAN Join request
|
||||||
|
func (o *Otaa) GenerateJoinRequest() ([]uint8, error) {
|
||||||
|
o.incrementDevNonce()
|
||||||
|
|
||||||
|
// TODO: Add checks
|
||||||
|
o.buf = o.buf[:0]
|
||||||
|
o.buf = append(o.buf, 0x00)
|
||||||
|
o.buf = append(o.buf, reverseBytes(o.AppEUI[:])...)
|
||||||
|
o.buf = append(o.buf, reverseBytes(o.DevEUI[:])...)
|
||||||
|
o.buf = append(o.buf, o.devNonce[:]...)
|
||||||
|
mic := genPayloadMIC(o.buf, o.AppKey)
|
||||||
|
o.buf = append(o.buf, mic[:]...)
|
||||||
|
|
||||||
|
return o.buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeJoinAccept Decodes a Lora Join Accept packet
|
||||||
|
func (o *Otaa) DecodeJoinAccept(phyPload []uint8, s *Session) error {
|
||||||
|
if len(phyPload) < 12 {
|
||||||
|
return ErrInvalidPacketLength
|
||||||
|
}
|
||||||
|
data := phyPload[1:] // Remove trailing 0x20
|
||||||
|
|
||||||
|
// Prepare AES Cipher
|
||||||
|
block, err := aes.NewCipher(o.AppKey[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buf := make([]byte, len(data))
|
||||||
|
for k := 0; k < len(data)/aes.BlockSize; k++ {
|
||||||
|
block.Encrypt(buf[k*aes.BlockSize:], data[k*aes.BlockSize:])
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(o.appNonce[:], buf[0:3])
|
||||||
|
copy(o.NetID[:], buf[3:6])
|
||||||
|
copy(s.DevAddr[:], buf[6:10])
|
||||||
|
s.DLSettings = buf[10]
|
||||||
|
s.RXDelay = buf[11]
|
||||||
|
|
||||||
|
if len(buf) > 16 {
|
||||||
|
copy(s.CFList[:], buf[12:28])
|
||||||
|
}
|
||||||
|
rxMic := buf[len(buf)-4:]
|
||||||
|
|
||||||
|
dataMic := []byte{}
|
||||||
|
dataMic = append(dataMic, phyPload[0])
|
||||||
|
dataMic = append(dataMic, o.appNonce[:]...)
|
||||||
|
dataMic = append(dataMic, o.NetID[:]...)
|
||||||
|
dataMic = append(dataMic, s.DevAddr[:]...)
|
||||||
|
dataMic = append(dataMic, s.DLSettings)
|
||||||
|
dataMic = append(dataMic, s.RXDelay)
|
||||||
|
dataMic = append(dataMic, s.CFList[:]...)
|
||||||
|
computedMic := genPayloadMIC(dataMic[:], o.AppKey)
|
||||||
|
if !bytes.Equal(computedMic[:], rxMic[:]) {
|
||||||
|
return ErrInvalidMic
|
||||||
|
}
|
||||||
|
// Generate NwkSKey
|
||||||
|
// NwkSKey = aes128_encrypt(AppKey, 0x01|AppNonce|NetID|DevNonce|pad16)
|
||||||
|
sKey := []byte{}
|
||||||
|
sKey = append(sKey, 0x01)
|
||||||
|
sKey = append(sKey, o.appNonce[:]...)
|
||||||
|
sKey = append(sKey, o.NetID[:]...)
|
||||||
|
sKey = append(sKey, o.devNonce[:]...)
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
sKey = append(sKey, 0x00) // PAD to 16
|
||||||
|
}
|
||||||
|
block.Encrypt(buf, sKey)
|
||||||
|
copy(s.NwkSKey[:], buf[0:16])
|
||||||
|
|
||||||
|
// Generate AppSKey
|
||||||
|
// AppSKey = aes128_encrypt(AppKey, 0x02|AppNonce|NetID|DevNonce|pad16)
|
||||||
|
sKey[0] = 0x02
|
||||||
|
block.Encrypt(buf, sKey)
|
||||||
|
copy(s.AppSKey[:], buf[0:16])
|
||||||
|
|
||||||
|
// Reset counters
|
||||||
|
s.FCntDown = 0
|
||||||
|
s.FCntUp = 0
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package region
|
||||||
|
|
||||||
|
import "tinygo.org/x/drivers/lora"
|
||||||
|
|
||||||
|
const (
|
||||||
|
AU915_DEFAULT_PREAMBLE_LEN = 8
|
||||||
|
AU915_DEFAULT_TX_POWER_DBM = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegionSettingsAU915 struct {
|
||||||
|
joinRequestChannel *Channel
|
||||||
|
joinAcceptChannel *Channel
|
||||||
|
uplinkChannel *Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
func AU915() *RegionSettingsAU915 {
|
||||||
|
return &RegionSettingsAU915{
|
||||||
|
joinRequestChannel: &Channel{lora.MHz_916_8,
|
||||||
|
lora.Bandwidth_125_0,
|
||||||
|
lora.SpreadingFactor9,
|
||||||
|
lora.CodingRate4_5,
|
||||||
|
AU915_DEFAULT_PREAMBLE_LEN,
|
||||||
|
AU915_DEFAULT_TX_POWER_DBM},
|
||||||
|
joinAcceptChannel: &Channel{lora.MHz_923_3,
|
||||||
|
lora.Bandwidth_500_0,
|
||||||
|
lora.SpreadingFactor9,
|
||||||
|
lora.CodingRate4_5,
|
||||||
|
AU915_DEFAULT_PREAMBLE_LEN,
|
||||||
|
AU915_DEFAULT_TX_POWER_DBM},
|
||||||
|
uplinkChannel: &Channel{lora.MHz_916_8,
|
||||||
|
lora.Bandwidth_125_0,
|
||||||
|
lora.SpreadingFactor9,
|
||||||
|
lora.CodingRate4_5,
|
||||||
|
AU915_DEFAULT_PREAMBLE_LEN,
|
||||||
|
AU915_DEFAULT_TX_POWER_DBM},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegionSettingsAU915) JoinRequestChannel() *Channel {
|
||||||
|
return r.joinRequestChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegionSettingsAU915) JoinAcceptChannel() *Channel {
|
||||||
|
return r.joinAcceptChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegionSettingsAU915) UplinkChannel() *Channel {
|
||||||
|
return r.uplinkChannel
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package region
|
||||||
|
|
||||||
|
import "tinygo.org/x/drivers/lora"
|
||||||
|
|
||||||
|
const (
|
||||||
|
EU868_DEFAULT_PREAMBLE_LEN = 8
|
||||||
|
EU868_DEFAULT_TX_POWER_DBM = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegionSettingsEU868 struct {
|
||||||
|
joinRequestChannel *Channel
|
||||||
|
joinAcceptChannel *Channel
|
||||||
|
uplinkChannel *Channel
|
||||||
|
}
|
||||||
|
|
||||||
|
func EU868() *RegionSettingsEU868 {
|
||||||
|
return &RegionSettingsEU868{
|
||||||
|
joinRequestChannel: &Channel{lora.MHz_868_1,
|
||||||
|
lora.Bandwidth_125_0,
|
||||||
|
lora.SpreadingFactor9,
|
||||||
|
lora.CodingRate4_7,
|
||||||
|
EU868_DEFAULT_PREAMBLE_LEN,
|
||||||
|
EU868_DEFAULT_TX_POWER_DBM},
|
||||||
|
joinAcceptChannel: &Channel{lora.MHz_868_1,
|
||||||
|
lora.Bandwidth_125_0,
|
||||||
|
lora.SpreadingFactor9,
|
||||||
|
lora.CodingRate4_7,
|
||||||
|
EU868_DEFAULT_PREAMBLE_LEN,
|
||||||
|
EU868_DEFAULT_TX_POWER_DBM},
|
||||||
|
uplinkChannel: &Channel{lora.MHz_868_1,
|
||||||
|
lora.Bandwidth_125_0,
|
||||||
|
lora.SpreadingFactor9,
|
||||||
|
lora.CodingRate4_7,
|
||||||
|
EU868_DEFAULT_PREAMBLE_LEN,
|
||||||
|
EU868_DEFAULT_TX_POWER_DBM},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegionSettingsEU868) JoinRequestChannel() *Channel {
|
||||||
|
return r.joinRequestChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegionSettingsEU868) JoinAcceptChannel() *Channel {
|
||||||
|
return r.joinAcceptChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RegionSettingsEU868) UplinkChannel() *Channel {
|
||||||
|
return r.uplinkChannel
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package region
|
||||||
|
|
||||||
|
type Channel struct {
|
||||||
|
Frequency uint32
|
||||||
|
Bandwidth uint8
|
||||||
|
SpreadingFactor uint8
|
||||||
|
CodingRate uint8
|
||||||
|
PreambleLength uint16
|
||||||
|
TxPowerDBm int8
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegionSettings interface {
|
||||||
|
JoinRequestChannel() *Channel
|
||||||
|
JoinAcceptChannel() *Channel
|
||||||
|
UplinkChannel() *Channel
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
package lorawan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Session is used to store session data of a LoRaWAN session
|
||||||
|
type Session struct {
|
||||||
|
NwkSKey [16]uint8
|
||||||
|
AppSKey [16]uint8
|
||||||
|
DevAddr [4]uint8
|
||||||
|
FCntDown uint32
|
||||||
|
FCntUp uint32
|
||||||
|
CFList [16]uint8
|
||||||
|
RXDelay uint8
|
||||||
|
DLSettings uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDevAddr configures the Session DevAddr
|
||||||
|
func (s *Session) SetDevAddr(devAddr []uint8) error {
|
||||||
|
if len(devAddr) != 4 {
|
||||||
|
return ErrInvalidDevAddrLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(s.DevAddr[:], devAddr)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDevAddr returns the Session DevAddr
|
||||||
|
func (s *Session) GetDevAddr() string {
|
||||||
|
return hex.EncodeToString(s.DevAddr[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNwkSKey configures the Session NwkSKey
|
||||||
|
func (s *Session) SetNwkSKey(nwkSKey []uint8) error {
|
||||||
|
if len(nwkSKey) != 16 {
|
||||||
|
return ErrInvalidNwkSKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(s.NwkSKey[:], nwkSKey)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNwkSKey returns the Session NwkSKey
|
||||||
|
func (s *Session) GetNwkSKey() string {
|
||||||
|
return hex.EncodeToString(s.NwkSKey[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAppSKey configures the Session AppSKey
|
||||||
|
func (s *Session) SetAppSKey(appSKey []uint8) error {
|
||||||
|
if len(appSKey) != 16 {
|
||||||
|
return ErrInvalidAppSKeyLength
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(s.AppSKey[:], appSKey)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppSKey returns the Session AppSKey
|
||||||
|
func (s *Session) GetAppSKey() string {
|
||||||
|
return hex.EncodeToString(s.AppSKey[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenMessage generates an uplink message.
|
||||||
|
func (s *Session) GenMessage(dir uint8, payload []uint8) ([]uint8, error) {
|
||||||
|
var buf []uint8
|
||||||
|
buf = append(buf, 0b01000000) // FHDR Unconfirmed up
|
||||||
|
buf = append(buf, s.DevAddr[:]...)
|
||||||
|
|
||||||
|
// FCtl : No ADR, No RFU, No ACK, No FPending, No FOpt
|
||||||
|
buf = append(buf, 0x00)
|
||||||
|
|
||||||
|
// FCnt Up
|
||||||
|
buf = append(buf, uint8(s.FCntUp&0xFF), uint8((s.FCntUp>>8)&0xFF))
|
||||||
|
|
||||||
|
// FPort=1
|
||||||
|
buf = append(buf, 0x01)
|
||||||
|
|
||||||
|
fCnt := uint32(0)
|
||||||
|
if dir == 0 {
|
||||||
|
fCnt = s.FCntUp
|
||||||
|
s.FCntUp++
|
||||||
|
} else {
|
||||||
|
fCnt = s.FCntDown
|
||||||
|
}
|
||||||
|
data, err := s.genFRMPayload(dir, fCnt, payload, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = append(buf, data[:]...)
|
||||||
|
|
||||||
|
mic := calcMessageMIC(buf, s.NwkSKey, dir, s.DevAddr[:], fCnt, uint8(len(buf)))
|
||||||
|
buf = append(buf, mic[:]...)
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) genFRMPayload(dir uint8, fCnt uint32, payload []byte, isFOpts bool) ([]byte, error) {
|
||||||
|
k := len(payload) / aes.BlockSize
|
||||||
|
if len(payload)%aes.BlockSize != 0 {
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
if k > math.MaxUint8 {
|
||||||
|
return nil, ErrFrmPayloadTooLarge
|
||||||
|
}
|
||||||
|
encrypted := make([]byte, 0, k*16)
|
||||||
|
cipher, err := aes.NewCipher(s.AppSKey[:])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var a [aes.BlockSize]byte
|
||||||
|
a[0] = 0x01
|
||||||
|
a[5] = dir
|
||||||
|
copy(a[6:10], s.DevAddr[:])
|
||||||
|
binary.LittleEndian.PutUint32(a[10:14], fCnt)
|
||||||
|
var ss [aes.BlockSize]byte
|
||||||
|
var b [aes.BlockSize]byte
|
||||||
|
for i := uint8(0); i < uint8(k); i++ {
|
||||||
|
copy(b[:], payload[i*aes.BlockSize:])
|
||||||
|
if !isFOpts {
|
||||||
|
a[15] = i + 1
|
||||||
|
}
|
||||||
|
cipher.Encrypt(ss[:], a[:])
|
||||||
|
for j := 0; j < aes.BlockSize; j++ {
|
||||||
|
b[j] = b[j] ^ ss[j]
|
||||||
|
}
|
||||||
|
encrypted = append(encrypted, b[:]...)
|
||||||
|
}
|
||||||
|
return encrypted[:len(payload)], nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package lora
|
||||||
|
|
||||||
|
type Radio interface {
|
||||||
|
Reset()
|
||||||
|
Tx(pkt []uint8, timeoutMs uint32) error
|
||||||
|
Rx(timeoutMs uint32) ([]uint8, error)
|
||||||
|
SetFrequency(freq uint32)
|
||||||
|
SetIqMode(mode uint8)
|
||||||
|
SetCodingRate(cr uint8)
|
||||||
|
SetBandwidth(bw uint8)
|
||||||
|
SetCrc(enable bool)
|
||||||
|
SetSpreadingFactor(sf uint8)
|
||||||
|
SetPreambleLength(plen uint16)
|
||||||
|
SetTxPower(txpow int8)
|
||||||
|
SetSyncWord(syncWord uint16)
|
||||||
|
SetPublicNetwork(enable bool)
|
||||||
|
SetHeaderType(headerType uint8)
|
||||||
|
LoraConfig(cnf Config)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package lora
|
||||||
|
|
||||||
|
const (
|
||||||
|
RadioEventRxDone = iota
|
||||||
|
RadioEventTxDone
|
||||||
|
RadioEventTimeout
|
||||||
|
RadioEventWatchdog
|
||||||
|
RadioEventCrcError
|
||||||
|
RadioEventUnhandled
|
||||||
|
)
|
||||||
|
|
||||||
|
// RadioEvent are used for communicating in the radio Event Channel
|
||||||
|
type RadioEvent struct {
|
||||||
|
EventType int
|
||||||
|
IRQStatus uint16
|
||||||
|
EventData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRadioEvent() returns a new RadioEvent that can be used in the RadioChannel
|
||||||
|
func NewRadioEvent(eType int, irqStatus uint16, eData []byte) RadioEvent {
|
||||||
|
r := RadioEvent{EventType: eType, IRQStatus: irqStatus, EventData: eData}
|
||||||
|
return r
|
||||||
|
}
|
||||||
+16
-1
@@ -32,7 +32,7 @@ func (d Device) Connected() bool {
|
|||||||
|
|
||||||
// Configure sets up the device for communication.
|
// Configure sets up the device for communication.
|
||||||
func (d Device) Configure() error {
|
func (d Device) Configure() error {
|
||||||
return d.bus.WriteRegister(uint8(d.Address), PWR_MGMT_1, []uint8{0})
|
return d.SetClockSource(CLOCK_INTERNAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAcceleration reads the current acceleration from the device and returns
|
// ReadAcceleration reads the current acceleration from the device and returns
|
||||||
@@ -78,3 +78,18 @@ func (d Device) ReadRotation() (x int32, y int32, z int32) {
|
|||||||
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 2048 * 1000
|
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / 2048 * 1000
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetClockSource allows the user to configure the clock source.
|
||||||
|
func (d Device) SetClockSource(source uint8) error {
|
||||||
|
return d.bus.WriteRegister(uint8(d.Address), PWR_MGMT_1, []uint8{source})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFullScaleGyroRange allows the user to configure the scale range for the gyroscope.
|
||||||
|
func (d Device) SetFullScaleGyroRange(rng uint8) error {
|
||||||
|
return d.bus.WriteRegister(uint8(d.Address), GYRO_CONFIG, []uint8{rng})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFullScaleAccelRange allows the user to configure the scale range for the accelerometer.
|
||||||
|
func (d Device) SetFullScaleAccelRange(rng uint8) error {
|
||||||
|
return d.bus.WriteRegister(uint8(d.Address), ACCEL_CONFIG, []uint8{rng})
|
||||||
|
}
|
||||||
|
|||||||
@@ -98,6 +98,29 @@ const (
|
|||||||
I2C_PER3_DO = 0x66
|
I2C_PER3_DO = 0x66
|
||||||
I2C_MST_DELAY_CT = 0x67
|
I2C_MST_DELAY_CT = 0x67
|
||||||
|
|
||||||
|
// Clock settings
|
||||||
|
CLOCK_INTERNAL = 0x00
|
||||||
|
CLOCK_PLL_XGYRO = 0x01
|
||||||
|
CLOCK_PLL_YGYRO = 0x02
|
||||||
|
CLOCK_PLL_ZGYRO = 0x03
|
||||||
|
CLOCK_PLL_EXTERNAL_32_768_KZ = 0x04
|
||||||
|
CLOCK_PLL_EXTERNAL_19_2_MHZ = 0x05
|
||||||
|
CLOCK_RESERVED = 0x06
|
||||||
|
CLOCK_STOP = 0x07
|
||||||
|
|
||||||
|
// Accelerometer settings
|
||||||
|
AFS_RANGE_2G = 0x00
|
||||||
|
AFS_RANGE_4G = 0x01
|
||||||
|
AFS_RANGE_8G = 0x02
|
||||||
|
AFS_RANGE_16G = 0x03
|
||||||
|
|
||||||
|
// Gyroscope settings
|
||||||
|
FS_RANGE_250 = 0x00
|
||||||
|
FS_RANGE_500 = 0x01
|
||||||
|
FS_RANGE_1000 = 0x02
|
||||||
|
FS_RANGE_2000 = 0x03
|
||||||
|
|
||||||
|
// other registers
|
||||||
SIGNAL_PATH_RES = 0x68 // Signal path reset
|
SIGNAL_PATH_RES = 0x68 // Signal path reset
|
||||||
USER_CTRL = 0x6A // User control
|
USER_CTRL = 0x6A // User control
|
||||||
PWR_MGMT_1 = 0x6B // Power Management 1
|
PWR_MGMT_1 = 0x6B // Power Management 1
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
// Package mpu6886 provides a driver for the MPU6886 accelerometer and gyroscope
|
||||||
|
// made by InvenSense.
|
||||||
|
//
|
||||||
|
// Datasheet:
|
||||||
|
// https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/datasheet/core/MPU-6886-000193%2Bv1.1_GHIC_en.pdf
|
||||||
|
package mpu6886 // import "tinygo.org/x/drivers/mpu6886"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"tinygo.org/x/drivers"
|
||||||
|
)
|
||||||
|
|
||||||
|
const WhoAmI = 0x19
|
||||||
|
|
||||||
|
var errNotConnected = errors.New("mpu6886: failed to communicate with a sensor")
|
||||||
|
|
||||||
|
// Device wraps an I2C connection to a MPU6886 device.
|
||||||
|
type Device struct {
|
||||||
|
bus drivers.I2C
|
||||||
|
Address uint16
|
||||||
|
aRange uint8
|
||||||
|
gRange uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config contains settings for filtering, sampling, and modes of operation
|
||||||
|
type Config struct {
|
||||||
|
AccelRange uint8
|
||||||
|
GyroRange uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new MPU6886 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: DefaultAddress}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connected returns whether a MPU6886 has been found.
|
||||||
|
// It does a "who am I" request and checks the response.
|
||||||
|
func (d *Device) Connected() bool {
|
||||||
|
data := []byte{0}
|
||||||
|
d.bus.Tx(d.Address, []byte{WHO_AM_I}, data)
|
||||||
|
return data[0] == WhoAmI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure sets up the device for communication.
|
||||||
|
func (d *Device) Configure(config Config) (err error) {
|
||||||
|
if config.AccelRange < 4 {
|
||||||
|
d.aRange = config.AccelRange
|
||||||
|
}
|
||||||
|
if config.GyroRange < 4 {
|
||||||
|
d.gRange = config.GyroRange
|
||||||
|
}
|
||||||
|
|
||||||
|
if !d.Connected() {
|
||||||
|
return errNotConnected
|
||||||
|
}
|
||||||
|
// This initialization sequence is borrowed from Arduino M5Stack library
|
||||||
|
// Zero register
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{PWR_MGMT_1, 0x00}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
// Set DEVICE_RESET bit
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{PWR_MGMT_1, 0x80}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
// Set CLKSEL to 1 - Auto selects the best available clock source
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{PWR_MGMT_1, 0x01}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
// Set ACCEL_FS_SEL
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{ACCEL_CONFIG, d.aRange << 3}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
// Set FS_SEL
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{GYRO_CONFIG, d.gRange << 3}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
// default: 0x80, set DLPF_CFG to 001 (Low Pass Filter)
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{CONFIG, 0x01}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
// Set sample rate divisor, sample rate is ~ 170 Hz
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{SMPLRT_DIV, 0x05}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
// Set Interupt pin
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{INT_PIN_CFG, 0x22}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
// Enable DATA_RDY_INT_EN
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{INT_ENABLE, 0x01}, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadTemperature returns the temperature in Celsius millidegrees (°C/1000).
|
||||||
|
func (d *Device) ReadTemperature() (t int32, err error) {
|
||||||
|
data := make([]byte, 2)
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{TEMP_OUT_H}, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rawTemperature := int32(int16((uint16(data[0]) << 8) | uint16(data[1])))
|
||||||
|
// The formula to convert to degrre of Celsius is
|
||||||
|
// T_C = T_raw / 326.8 + 25.0
|
||||||
|
// This formula should not overflow
|
||||||
|
t = rawTemperature*10000/3268 + 25000
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAcceleration reads the current acceleration from the device and returns
|
||||||
|
// it 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) ReadAcceleration() (x int32, y int32, z int32, err error) {
|
||||||
|
data := make([]byte, 6)
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{ACCEL_XOUT_H}, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Now do two things:
|
||||||
|
// 1. merge the two values to a 16-bit number (and cast to a 32-bit integer)
|
||||||
|
// 2. scale the value to bring it in the -1000000..1000000 range.
|
||||||
|
// This is done with a trick. What we do here is essentially multiply by
|
||||||
|
// 1000000 and divide by 16384 to get the original scale, but to avoid
|
||||||
|
// overflow we do it at 1/64 of the value:
|
||||||
|
// 1000000 / 64 = 15625
|
||||||
|
// 16384 / 64 = 256
|
||||||
|
divider := int32(1)
|
||||||
|
switch d.aRange {
|
||||||
|
case AFS_RANGE_2_G:
|
||||||
|
divider = 256
|
||||||
|
case AFS_RANGE_4_G:
|
||||||
|
divider = 128
|
||||||
|
case AFS_RANGE_8_G:
|
||||||
|
divider = 64
|
||||||
|
case AFS_RANGE_16_G:
|
||||||
|
divider = 32
|
||||||
|
}
|
||||||
|
x = int32(int16((uint16(data[0])<<8)|uint16(data[1]))) * 15625 / divider
|
||||||
|
y = int32(int16((uint16(data[2])<<8)|uint16(data[3]))) * 15625 / divider
|
||||||
|
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / divider
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadRotation reads the current rotation from the device and returns it in
|
||||||
|
// µ°/s (micro-degrees/sec). This means that if you were to do a complete
|
||||||
|
// rotation along one axis and while doing so integrate all values over time,
|
||||||
|
// you would get a value close to 360000000.
|
||||||
|
func (d *Device) ReadRotation() (x int32, y int32, z int32, err error) {
|
||||||
|
data := make([]byte, 6)
|
||||||
|
if err = d.bus.Tx(d.Address, []byte{GYRO_XOUT_H}, data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// First the value is converted from a pair of bytes to a signed 16-bit
|
||||||
|
// value and then to a signed 32-bit value to avoid integer overflow.
|
||||||
|
// Then the value is scaled to µ°/s (micro-degrees per second).
|
||||||
|
// This is done in the following steps:
|
||||||
|
// 1. Multiply by 250 * 1000_000
|
||||||
|
// 2. Divide by 32768
|
||||||
|
// The following calculation (x * 15625 / 2048 * 1000) is essentially the
|
||||||
|
// same but avoids overflow. First both operations are divided by 16 leading
|
||||||
|
// to multiply by 15625000 and divide by 2048, and then part of the multiply
|
||||||
|
// is done after the divide instead of before.
|
||||||
|
divider := int32(1)
|
||||||
|
switch d.gRange {
|
||||||
|
case GFS_RANGE_250:
|
||||||
|
divider = 2048
|
||||||
|
case GFS_RANGE_500:
|
||||||
|
divider = 1024
|
||||||
|
case GFS_RANGE_1000:
|
||||||
|
divider = 512
|
||||||
|
case GFS_RANGE_2000:
|
||||||
|
divider = 256
|
||||||
|
}
|
||||||
|
x = int32(int16((uint16(data[0])<<8)|uint16(data[1]))) * 15625 / divider * 1000
|
||||||
|
y = int32(int16((uint16(data[2])<<8)|uint16(data[3]))) * 15625 / divider * 1000
|
||||||
|
z = int32(int16((uint16(data[4])<<8)|uint16(data[5]))) * 15625 / divider * 1000
|
||||||
|
return
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user