Compare commits

..

17 Commits

Author SHA1 Message Date
deadprogram b6b5668945 examples: correct missing const for lora atcmd
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 19:13:18 +01:00
deadprogram 24b6f0f296 examples: use shorter names for Lora radio functions
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 18:51:03 +01:00
deadprogram 38ba7772e2 sx126x, sx127x: remove extra Lora in functions to avoid stuttering
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 17:17:05 +01:00
deadprogram 6df6bf4880 examples: add SEND/RECV to lora at-cmd for LoRa P2P/P2MP (not LoRaWAN) testing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 14:31:25 +01:00
deadprogram 8cfe5ebc4a examples: lora atcmd minimal rx and tx
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 14:31:25 +01:00
deadprogram f098853465 examples: add minimal README to at-cmd
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 14:31:25 +01:00
deadprogram dc953b09b6 examples: lora atcmd compiled for simulator, sx126x, and sx127x
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 14:31:25 +01:00
deadprogram 59ff8a4585 examples: further work on at-lora
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 14:31:25 +01:00
deadprogram 125d8b6530 examples: WIP on adding LoRa AT command set implementation
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 14:31:25 +01:00
deadprogram 1832e40f4d sx127x: correct header type implicit/explcit as they were reversed
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-08 14:30:55 +01:00
deadprogram 9af4640788 sx126x: add Reset() and needed pin
Signed-off-by: deadprogram <ron@hybridgroup.com>
2023-01-06 14:15:38 +01:00
deadprogram 65677a3836 sx127x: lora rx/tx example working, now assumes pybadge+featherwing
Signed-off-by: deadprogram <ron@hybridgroup.com>
2022-12-30 10:09:47 +01:00
deadprogram f67130b52d lora: created shared RadioEvent
Signed-off-by: deadprogram <ron@hybridgroup.com>
2022-12-30 10:09:47 +01:00
deadprogram d94003bd3a lora: move shared config for sx126x/sx127x to single package
Signed-off-by: deadprogram <ron@hybridgroup.com>
2022-12-30 10:09:47 +01:00
Olivier Fauchon a421318a4d sx127x: Ajout du support d'interruptions RXTimeout (DIO1) 2022-12-23 11:07:55 +01:00
Olivier Fauchon deb22c4a57 sx127x: Major cleanup/rewrite, and tests pass with lorawan stack tests 2022-12-23 11:07:55 +01:00
Olivier Fauchon 67d2af5d45 sx127x: Driver for Semtech sx127x radio modules
This first version of the driver has been tested with BB-FRM9x module
2022-12-23 11:07:55 +01:00
142 changed files with 2241 additions and 6177 deletions
+2 -5
View File
@@ -11,13 +11,10 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: ghcr.io/tinygo-org/tinygo-dev container: tinygo/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@v3 uses: actions/checkout@v2
- 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
View File
@@ -1,159 +1,3 @@
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 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved. Copyright (c) 2018-2022 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
+240 -3
View File
@@ -7,11 +7,248 @@ 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
@go run ./smoketest.go -xtensa=$(XTENSA) smoketest.sh tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/amg88xx
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/main.go
@md5sum ./build/test.hex
tinygo build -size short -o ./build/test.hex -target=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
+96 -3
View File
@@ -50,10 +50,103 @@ func main() {
} }
``` ```
## Supported devices ## Currently supported devices
There are currently 96 devices supported. For the complete list, please see: The following 90 devices are supported.
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 |
| [SH1106 OLED display](https://www.velleman.eu/downloads/29/infosheets/sh1106_datasheet.pdf) | I2C / SPI |
| [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
-172
View File
@@ -1,172 +0,0 @@
// 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))
}
-22
View File
@@ -1,22 +0,0 @@
// 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}
}
-198
View File
@@ -1,198 +0,0 @@
// 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))
}
-95
View File
@@ -1,95 +0,0 @@
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
}
-169
View File
@@ -1,169 +0,0 @@
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&reg_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&reg_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&reg_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&reg_read != 0 {
r.host.value = newValue
r.host.cached = true
}
return err
}
-208
View File
@@ -1,208 +0,0 @@
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
-16
View File
@@ -6,7 +6,6 @@
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"
@@ -124,21 +123,6 @@ 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})
-4
View File
@@ -28,7 +28,3 @@ 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
)
-54
View File
@@ -1,54 +0,0 @@
#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
}
-57
View File
@@ -1,57 +0,0 @@
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)
}
}
-16
View File
@@ -12,19 +12,3 @@ 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
)
-89
View File
@@ -1,89 +0,0 @@
// 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
}
+2 -36
View File
@@ -73,17 +73,7 @@ func (d *Device) SetRunning(isRunning bool) error {
return nil return nil
} }
// SetTime sets the date and time in the DS3231. The DS3231 hardware supports // SetTime sets the date and time in the DS3231
// 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)
@@ -102,10 +92,6 @@ 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
@@ -154,27 +140,7 @@ func (d *Device) ReadTemperature() (int32, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
return milliCelsius(data[0], data[1]), nil return int32(data[0])*1000 + int32((data[1]>>6)*25)*10, 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
-76
View File
@@ -1,76 +0,0 @@
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)
}
}
-66
View File
@@ -1,66 +0,0 @@
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)
}
}
-3
View File
@@ -27,9 +27,6 @@ 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)
} }
} }
-17
View File
@@ -1,17 +0,0 @@
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())
}
-58
View File
@@ -1,58 +0,0 @@
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)
}
}
}
+29
View File
@@ -0,0 +1,29 @@
# AT-CMD implementation of at-lora command set
```
$ tinygo monitor
Connected to /dev/ttyACM0. Press Ctrl-C to exit.
+AT: OK
+VER: 0.0.1 (sx127x v18)
```
# Building
## Simulator
```
tinygo flash -target pico ./examples/lora/atcmd/
```
## PyBadge with LoRa Featherwing
```
tinygo flash -target pybadge -tags featherwing ./examples/lora/atcmd/
```
## LoRa-E5
```
tinygo flash -target lorae5 ./examples/lora/atcmd/
```
@@ -3,9 +3,6 @@ package main
import ( import (
"encoding/hex" "encoding/hex"
"strings" "strings"
"tinygo.org/x/drivers/examples/lora/lorawan/common"
"tinygo.org/x/drivers/lora/lorawan"
) )
// Use to test if connection to module is OK. // Use to test if connection to module is OK.
@@ -15,7 +12,7 @@ func quicktest() {
// Check firmware version. // Check firmware version.
func version() { func version() {
writeCommandOutput("VER", common.CurrentVersion()+" ("+common.FirmwareVersion()+")") writeCommandOutput("VER", currentVersion()+" ("+firmwareVersion()+")")
} }
// Use to check the ID of the LoRaWAN module, or change the ID. // Use to check the ID of the LoRaWAN module, or change the ID.
@@ -26,42 +23,13 @@ func id(args string) error {
param, val, hasComma := strings.Cut(args, ",") param, val, hasComma := strings.Cut(args, ",")
if hasComma { if hasComma {
// set // 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 { switch param {
case "DevAddr": case "DevAddr":
if err := session.SetDevAddr(hexdata); err != nil { writeCommandOutput(cmd, "DevAddr, "+val)
writeCommandOutput(cmd, err.Error())
return err
}
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr())
case "DevEui": case "DevEui":
if err := otaa.SetDevEUI(hexdata); err != nil { writeCommandOutput(cmd, "DevEui, "+val)
writeCommandOutput(cmd, err.Error())
return err
}
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI())
case "AppEui": case "AppEui":
if err := otaa.SetAppEUI(hexdata); err != nil { writeCommandOutput(cmd, "AppEui, "+val)
writeCommandOutput(cmd, err.Error())
return err
}
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI())
default: default:
return errInvalidCommand return errInvalidCommand
} }
@@ -72,15 +40,15 @@ func id(args string) error {
// get // get
switch param { switch param {
case "DevAddr": case "DevAddr":
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr()) writeCommandOutput(cmd, "DevAddr, xx:xx:xx:xx")
case "DevEui": case "DevEui":
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI()) writeCommandOutput(cmd, "DevEui, xx:xx:xx:xx:xx:xx:xx:xx")
case "AppEui": case "AppEui":
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI()) writeCommandOutput(cmd, "AppEui, xx:xx:xx:xx:xx:xx:xx:xx")
default: default:
writeCommandOutput(cmd, "DevAddr, "+session.GetDevAddr()) writeCommandOutput(cmd, "DevAddr, xx:xx:xx:xx")
writeCommandOutput(cmd, "DevEui, "+otaa.GetDevEUI()) writeCommandOutput(cmd, "DevEui, xx:xx:xx:xx:xx:xx:xx:xx")
writeCommandOutput(cmd, "AppEui, "+otaa.GetAppEUI()) writeCommandOutput(cmd, "AppEui, xx:xx:xx:xx:xx:xx:xx:xx")
} }
return nil return nil
@@ -240,38 +208,9 @@ func rxwin1(setting string) error {
func key(setting string) error { func key(setting string) error {
cmd := "KEY" cmd := "KEY"
writeCommandOutput(cmd, setting)
// look for comma in args return nil
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 { func fdefault(setting string) error {
@@ -289,23 +228,9 @@ func mode(setting string) error {
} }
func join(setting string) error { func join(setting string) error {
// TODO: check that DevEUI, AppEUI, and AppKey have values
cmd := "JOIN" cmd := "JOIN"
writeCommandOutput(cmd, "Starting") 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") writeCommandOutput(cmd, "Done")
return nil return nil
} }
@@ -337,17 +262,6 @@ func delay(setting string) error {
func lw(setting string) error { func lw(setting string) error {
cmd := "LW" 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) writeCommandOutput(cmd, setting)
return nil return nil
@@ -462,7 +376,7 @@ func sendhex(data string) error {
func recv(setting string) error { func recv(setting string) error {
cmd := "RECV" cmd := "RECV"
data, err := common.Lorarx() data, err := lorarx()
if err != nil { if err != nil {
writeCommandOutput(cmd, "ERROR "+err.Error()) writeCommandOutput(cmd, "ERROR "+err.Error())
return err return err
@@ -475,7 +389,7 @@ func recv(setting string) error {
func recvhex(setting string) error { func recvhex(setting string) error {
cmd := "RECVHEX" cmd := "RECVHEX"
data, err := common.Lorarx() data, err := lorarx()
if err != nil { if err != nil {
writeCommandOutput(cmd, "ERROR "+err.Error()) writeCommandOutput(cmd, "ERROR "+err.Error())
return err return err
@@ -11,11 +11,6 @@ package main
import ( import (
"machine" "machine"
"time" "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 // change these to test a different UART or pins if available
@@ -25,10 +20,7 @@ var (
rx = machine.UART_RX_PIN rx = machine.UART_RX_PIN
input = make([]byte, 0, 64) input = make([]byte, 0, 64)
radio lora.Radio radio LoraRadio
session *lorawan.Session
otaa *lorawan.Otaa
defaultTimeout uint32 = 1000 defaultTimeout uint32 = 1000
) )
@@ -36,17 +28,11 @@ func main() {
uart.Configure(machine.UARTConfig{TX: tx, RX: rx}) uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
var err error var err error
radio, err = common.SetupLora() radio, err = setupLora()
if err != nil { if err != nil {
fail(err.Error()) fail(err.Error())
} }
session = &lorawan.Session{}
otaa = &lorawan.Otaa{}
lorawan.UseRadio(radio)
lorawan.UseRegionSettings(region.EU868())
for { for {
if uart.Buffered() > 0 { if uart.Buffered() > 0 {
data, _ := uart.ReadByte() data, _ := uart.ReadByte()
+11 -8
View File
@@ -1,6 +1,15 @@
package lora package main
type Radio interface { import (
"errors"
)
var (
errRadioNotFound = errors.New("radio not found")
errRxTimeout = errors.New("radio RX timeout")
)
type LoraRadio interface {
Reset() Reset()
Tx(pkt []uint8, timeoutMs uint32) error Tx(pkt []uint8, timeoutMs uint32) error
Rx(timeoutMs uint32) ([]uint8, error) Rx(timeoutMs uint32) ([]uint8, error)
@@ -10,10 +19,4 @@ type Radio interface {
SetBandwidth(bw uint8) SetBandwidth(bw uint8)
SetCrc(enable bool) SetCrc(enable bool)
SetSpreadingFactor(sf uint8) SetSpreadingFactor(sf uint8)
SetPreambleLength(plen uint16)
SetTxPower(txpow int8)
SetSyncWord(syncWord uint16)
SetPublicNetwork(enable bool)
SetHeaderType(headerType uint8)
LoraConfig(cnf Config)
} }
@@ -1,11 +1,9 @@
//go:build !featherwing && !lgt92 && !stm32wlx && !sx126x //go:build !featherwing && !gnse && !lorae5 && !nucleowl55jc
package common package main
import "tinygo.org/x/drivers/lora"
// do simulator setup here // do simulator setup here
func SetupLora() (lora.Radio, error) { func setupLora() (LoraRadio, error) {
return &SimLoraRadio{}, nil return &SimLoraRadio{}, nil
} }
@@ -29,12 +27,11 @@ func (sr *SimLoraRadio) SetCodingRate(cr uint8) {}
func (sr *SimLoraRadio) SetBandwidth(bw uint8) {} func (sr *SimLoraRadio) SetBandwidth(bw uint8) {}
func (sr *SimLoraRadio) SetCrc(enable bool) {} func (sr *SimLoraRadio) SetCrc(enable bool) {}
func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {} func (sr *SimLoraRadio) SetSpreadingFactor(sf uint8) {}
func (sr *SimLoraRadio) LoraConfig(cnf lora.Config) {}
func FirmwareVersion() string { func firmwareVersion() string {
return "simulator " + CurrentVersion() return "simulator " + currentVersion()
} }
func Lorarx() ([]byte, error) { func lorarx() ([]byte, error) {
return nil, nil return nil, nil
} }
+73
View File
@@ -0,0 +1,73 @@
//go:build gnse || lorae5 || nucleowl55jc
package main
import (
"device/stm32"
"machine"
"runtime/interrupt"
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
"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
)
// do sx126x setup here
func setupLora() (LoraRadio, error) {
loraRadio = sx126x.New(machine.SPI3)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create RF Switch
var radioSwitch rfswitch.CustomSwitch
loraRadio.SetRfSwitch(radioSwitch)
if state := loraRadio.DetectDevice(); !state {
return nil, errRadioNotFound
}
// Add interrupt handler for Radio IRQs
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
intr.Enable()
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit,
Preamble: 12,
Ldr: lora.LowDataRateOptimizeOff,
Iq: lora.IQStandard,
Crc: lora.CRCOn,
SyncWord: lora.SyncPrivate,
LoraTxPowerDBm: 20,
}
loraRadio.LoraConfig(loraConf)
return loraRadio, nil
}
// radioIntHandler will take care of radio interrupts
func radioIntHandler(intr interrupt.Interrupt) {
loraRadio.HandleInterrupt()
}
func firmwareVersion() string {
return "sx126x"
}
func lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
+85
View File
@@ -0,0 +1,85 @@
//go:build featherwing
package main
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 (
// We assume LoRa Featherwing module is connected to PyBadge:
rstPin = machine.D11
csPin = machine.D10
dio0Pin = machine.D6
dio1Pin = machine.D9
spi = machine.SPI0
loraRadio *sx127x.Device
)
// do sx127x setup here
func setupLora() (LoraRadio, error) {
rstPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
csPin.Configure(machine.PinConfig{Mode: machine.PinOutput})
dio0Pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
dio1Pin.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
spi.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
loraRadio = sx127x.New(spi, csPin, rstPin)
loraRadio.Reset()
if state := loraRadio.DetectDevice(); !state {
return nil, errRadioNotFound
}
// Setup DIO0 interrupt Handling
if err := dio0Pin.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO0 pin interrupt:", err.Error())
}
// Setup DIO1 interrupt Handling
if err := dio1Pin.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO1 pin interrupt:", err.Error())
}
// Prepare for Lora Operation
loraConf := lora.Config{
Freq: FREQ,
Bw: lora.Bandwidth_500_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)
return loraRadio, nil
}
func dioIrqHandler(machine.Pin) {
loraRadio.HandleInterrupt()
}
func firmwareVersion() string {
v := loraRadio.GetVersion()
return "sx127x v" + strconv.Itoa(int(v))
}
func lorarx() ([]byte, error) {
return loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
}
+7
View File
@@ -0,0 +1,7 @@
package main
const VERSION = "0.0.1"
func currentVersion() string {
return VERSION
}
@@ -1,8 +1,8 @@
//go:build nucleowl55jc //go:build nucleowl55jc
/* /*
Nucleo WL55JC1 Nucleo WL55JC1
RFSwitch RFSwitch
+-----------+---------+------------+------------+ +-----------+---------+------------+------------+
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 | | | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
@@ -13,48 +13,42 @@ RFSwitch
| RX | HIGH | LOW | HIGH | | RX | HIGH | LOW | HIGH |
+-----------+----------+-----------+------------+ +-----------+----------+-----------+------------+
*/ */
package sx126x package main
import ( import (
"machine" "machine"
"tinygo.org/x/drivers/sx126x"
) )
// RadioControl for Nucleo WL55JC1 board. type CustomSwitch struct {
type RadioControl struct {
STM32RadioControl
} }
func NewRadioControl() *RadioControl { func (s CustomSwitch) InitRFSwitch() {
return &RadioControl{STM32RadioControl{}}
}
// Init pins needed for controlling rx/tx
func (rc *RadioControl) Init() error {
machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput}) machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput}) machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput}) machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput})
return nil
} }
func (rc *RadioControl) SetRfSwitchMode(mode int) error { func (s CustomSwitch) SetRfSwitchMode(mode int) error {
switch mode { switch mode {
case RFSWITCH_TX_HP: case sx126x.RFSWITCH_TX_HP:
machine.PC4.Set(false) machine.PC4.Set(false)
machine.PC5.Set(true) machine.PC5.Set(true)
machine.PC3.Set(true) machine.PC3.Set(true)
case RFSWITCH_TX_LP: case sx126x.RFSWITCH_TX_LP:
machine.PC4.Set(true) machine.PC4.Set(true)
machine.PC5.Set(true) machine.PC5.Set(true)
machine.PC3.Set(true) machine.PC3.Set(true)
case RFSWITCH_RX: case sx126x.RFSWITCH_RX:
machine.PC4.Set(true) machine.PC4.Set(true)
machine.PC5.Set(false) machine.PC5.Set(false)
machine.PC3.Set(true) machine.PC3.Set(true)
}
}
return nil return nil
} }
-52
View File
@@ -1,52 +0,0 @@
# 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)
@@ -1,44 +0,0 @@
# 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
```
@@ -1,17 +0,0 @@
//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)
}
-109
View File
@@ -1,109 +0,0 @@
// 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++
}
}
@@ -1,14 +0,0 @@
//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
)
-13
View File
@@ -1,13 +0,0 @@
//go:build lgt92
package common
import "machine"
var (
rstPin = machine.PB0
csPin = machine.PA15
dio0Pin = machine.PC13
dio1Pin = machine.PB10
spi = machine.SPI0
)
-10
View File
@@ -1,10 +0,0 @@
package common
import (
"errors"
)
var (
errRadioNotFound = errors.New("radio not found")
errRxTimeout = errors.New("radio RX timeout")
)
-49
View File
@@ -1,49 +0,0 @@
//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)
}
-53
View File
@@ -1,53 +0,0 @@
//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)
}
-47
View File
@@ -1,47 +0,0 @@
//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)
}
-7
View File
@@ -1,7 +0,0 @@
package common
const VERSION = "0.0.1"
func CurrentVersion() string {
return VERSION
}
-22
View File
@@ -1,22 +0,0 @@
// 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)
}
}
-40
View File
@@ -1,40 +0,0 @@
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))
}
}
}
+542
View File
@@ -0,0 +1,542 @@
//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("==> ")
}
+17
View File
@@ -0,0 +1,17 @@
//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
}
+17
View File
@@ -0,0 +1,17 @@
//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
}
+17
View File
@@ -0,0 +1,17 @@
//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
}
+40
View File
@@ -0,0 +1,40 @@
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)
}
+17
View File
@@ -0,0 +1,17 @@
//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
}
+17
View File
@@ -0,0 +1,17 @@
//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
}
+17
View File
@@ -0,0 +1,17 @@
//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
}
@@ -0,0 +1,17 @@
//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
}
+17
View File
@@ -0,0 +1,17 @@
//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
}
@@ -5,6 +5,8 @@ import (
"machine" "machine"
"time" "time"
rfswitch "tinygo.org/x/drivers/examples/sx126x/rfswitch"
"tinygo.org/x/drivers/lora" "tinygo.org/x/drivers/lora"
"tinygo.org/x/drivers/sx126x" "tinygo.org/x/drivers/sx126x"
) )
@@ -22,11 +24,12 @@ 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(spi) loraRadio = sx126x.New(machine.SPI3)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262) loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create radio controller for target // Create RF Switch
loraRadio.SetRadioController(newRadioControl()) var radioSwitch rfswitch.CustomSwitch
loraRadio.SetRfSwitch(radioSwitch)
state := loraRadio.DetectDevice() state := loraRadio.DetectDevice()
if !state { if !state {
@@ -35,8 +38,8 @@ func main() {
// Prepare for Lora operation // Prepare for Lora operation
loraConf := lora.Config{ loraConf := lora.Config{
Freq: lora.MHz_868_1, Freq: FREQ,
Bw: lora.Bandwidth_125_0, Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9, Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7, Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit, HeaderType: lora.HeaderExplicit,
@@ -45,7 +48,7 @@ func main() {
Iq: lora.IQStandard, Iq: lora.IQStandard,
Crc: lora.CRCOn, Crc: lora.CRCOn,
SyncWord: lora.SyncPrivate, SyncWord: lora.SyncPrivate,
LoraTxPowerDBm: 20, LoraTxPowerDBm: 14,
} }
loraRadio.LoraConfig(loraConf) loraRadio.LoraConfig(loraConf)
@@ -73,5 +76,6 @@ func main() {
loraRadio.SetStandby() loraRadio.SetStandby()
time.Sleep(60 * time.Second) time.Sleep(60 * time.Second)
} }
} }
-19
View File
@@ -1,19 +0,0 @@
//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)
}
@@ -1,15 +0,0 @@
//go:build stm32wlx
package main
import (
"machine"
"tinygo.org/x/drivers/sx126x"
)
var spi = machine.SPI3
func newRadioControl() sx126x.RadioController {
return sx126x.NewRadioControl()
}
+23 -9
View File
@@ -4,13 +4,19 @@ 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/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
@@ -21,19 +27,23 @@ var (
txmsg = []byte("Hello TinyGO") txmsg = []byte("Hello TinyGO")
) )
func main() { // radioIntHandler will take care of radio interrupts
time.Sleep(3 * time.Second) func radioIntHandler(intr interrupt.Interrupt) {
loraRadio.HandleInterrupt()
}
func main() {
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(spi) loraRadio = sx126x.New(machine.SPI3)
loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262) loraRadio.SetDeviceType(sx126x.DEVICE_TYPE_SX1262)
// Create radio controller for target // Create RF Switch
loraRadio.SetRadioController(newRadioControl()) var radioSwitch rfswitch.CustomSwitch
loraRadio.SetRfSwitch(radioSwitch)
// Detect the device // Detect the device
state := loraRadio.DetectDevice() state := loraRadio.DetectDevice()
@@ -41,9 +51,13 @@ func main() {
panic("sx126x not detected.") panic("sx126x not detected.")
} }
// Add interrupt handler for Radio IRQs
intr := interrupt.New(stm32.IRQ_Radio_IRQ_Busy, radioIntHandler)
intr.Enable()
loraConf := lora.Config{ loraConf := lora.Config{
Freq: lora.MHz_868_1, Freq: FREQ,
Bw: lora.Bandwidth_125_0, Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9, Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7, Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit, HeaderType: lora.HeaderExplicit,
@@ -59,10 +73,10 @@ func main() {
var count uint var count uint
for { for {
start := time.Now() tStart := time.Now()
println("main: Receiving Lora for 10 seconds") println("main: Receiving Lora for 10 seconds")
for time.Since(start) < 10*time.Second { for int(time.Now().Sub(tStart).Seconds()) < 10 {
buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS) buf, err := loraRadio.Rx(LORA_DEFAULT_RXTIMEOUT_MS)
if err != nil { if err != nil {
println("RX Error: ", err) println("RX Error: ", err)
-29
View File
@@ -1,29 +0,0 @@
//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,
})
}
@@ -1,15 +0,0 @@
//go:build stm32wlx
package main
import (
"machine"
"tinygo.org/x/drivers/sx126x"
)
var spi = machine.SPI3
func newRadioControl() sx126x.RadioController {
return sx126x.NewRadioControl()
}
+60
View File
@@ -0,0 +1,60 @@
//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
}
+42
View File
@@ -0,0 +1,42 @@
//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
}
+54
View File
@@ -0,0 +1,54 @@
//go:build nucleowl55jc
/*
Nucleo WL55JC1
RFSwitch
+-----------+---------+------------+------------+
| | FE_CTRL1 | FE_CTRL2 | FE_CTRL3 |
| | (PC4) | (PC5) | (PC3) |
+-----------+----------+-----------+------------+
| TX_HP | LOW | HIGH | HIGH |
| TX_LP | HIGH | HIGH | HIGH |
| RX | HIGH | LOW | HIGH |
+-----------+----------+-----------+------------+
*/
package rfswitch
import (
"machine"
"tinygo.org/x/drivers/sx126x"
)
type CustomSwitch struct {
}
func (s CustomSwitch) InitRFSwitch() {
machine.PC4.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.PC5.Configure(machine.PinConfig{Mode: machine.PinOutput})
machine.PC3.Configure(machine.PinConfig{Mode: machine.PinOutput})
}
func (s CustomSwitch) SetRfSwitchMode(mode int) error {
switch mode {
case sx126x.RFSWITCH_TX_HP:
machine.PC4.Set(false)
machine.PC5.Set(true)
machine.PC3.Set(true)
case sx126x.RFSWITCH_TX_LP:
machine.PC4.Set(true)
machine.PC5.Set(true)
machine.PC3.Set(true)
case sx126x.RFSWITCH_RX:
machine.PC4.Set(true)
machine.PC5.Set(false)
machine.PC3.Set(true)
}
return nil
}
+18 -6
View File
@@ -11,6 +11,8 @@ import (
"tinygo.org/x/drivers/sx127x" "tinygo.org/x/drivers/sx127x"
) )
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
@@ -38,13 +40,13 @@ func main() {
println("# ----------------------") println("# ----------------------")
machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput}) machine.LED.Configure(machine.PinConfig{Mode: machine.PinOutput})
SX127X_PIN_RST.Configure(machine.PinConfig{Mode: machine.PinOutput}) SX127X_PIN_RST.Configure(machine.PinConfig{Mode: machine.PinOutput})
SX127X_PIN_CS.Configure(machine.PinConfig{Mode: machine.PinOutput})
SX127X_PIN_DIO0.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
SX127X_PIN_DIO1.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
SX127X_SPI.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0}) SX127X_SPI.Configure(machine.SPIConfig{Frequency: 500000, Mode: 0})
println("main: create and start SX127x driver") println("main: create and start SX127x driver")
loraRadio = sx127x.New(SX127X_SPI, SX127X_PIN_RST) loraRadio = sx127x.New(SX127X_SPI, SX127X_PIN_CS, SX127X_PIN_RST)
loraRadio.SetRadioController(sx127x.NewRadioControl(SX127X_PIN_CS, SX127X_PIN_DIO0, SX127X_PIN_DIO1))
loraRadio.Reset() loraRadio.Reset()
state := loraRadio.DetectDevice() state := loraRadio.DetectDevice()
if !state { if !state {
@@ -53,10 +55,20 @@ func main() {
println("main: sx127x found") println("main: sx127x found")
} }
// Setup DIO0 interrupt Handling
if err := SX127X_PIN_DIO0.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO0 pin interrupt:", err.Error())
}
// Setup DIO1 interrupt Handling
if err := SX127X_PIN_DIO1.SetInterrupt(machine.PinRising, dioIrqHandler); err != nil {
println("could not configure DIO1 pin interrupt:", err.Error())
}
// Prepare for Lora Operation // Prepare for Lora Operation
loraConf := lora.Config{ loraConf := lora.Config{
Freq: lora.MHz_868_1, Freq: FREQ,
Bw: lora.Bandwidth_125_0, Bw: lora.Bandwidth_500_0,
Sf: lora.SpreadingFactor9, Sf: lora.SpreadingFactor9,
Cr: lora.CodingRate4_7, Cr: lora.CodingRate4_7,
HeaderType: lora.HeaderExplicit, HeaderType: lora.HeaderExplicit,
-40
View File
@@ -1,40 +0,0 @@
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)
}
}
+7 -19
View File
@@ -128,29 +128,17 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
failMessage(err.Error())
}
func failMessage(msg string) {
for {
println(msg)
time.Sleep(1 * time.Second)
}
} }
+8 -23
View File
@@ -74,7 +74,6 @@ 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"
@@ -132,42 +131,28 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
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 address: " + ip.String()) message(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)
}
}
+8 -16
View File
@@ -65,7 +65,6 @@ 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))
@@ -102,33 +101,26 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
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 address: " + ip.String()) println(ip.String())
} }
// Returns an int >= min, < max // Returns an int >= min, < max
+9 -16
View File
@@ -71,7 +71,6 @@ 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))
@@ -114,33 +113,27 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
failMessage(err.Error())
}
func displayIP() { time.Sleep(2 * time.Second)
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 address: " + ip.String()) println(ip.String())
} }
// Returns an int >= min, < max // Returns an int >= min, < max
+9 -23
View File
@@ -61,7 +61,6 @@ func main() {
waitSerial() waitSerial()
connectToAP() connectToAP()
displayIP()
// now make UDP connection // now make UDP connection
ip := net.ParseIP(ntpHost) ip := net.ParseIP(ntpHost)
@@ -150,42 +149,29 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
failMessage(err.Error())
}
func displayIP() { time.Sleep(2 * time.Second)
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 address: " + ip.String()) message(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)
}
}
+9 -23
View File
@@ -56,7 +56,6 @@ func main() {
adaptor.Configure() adaptor.Configure()
connectToAP() connectToAP()
displayIP()
for { for {
sendBatch() sendBatch()
@@ -112,42 +111,29 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
failMessage(err.Error())
}
func displayIP() { time.Sleep(2 * time.Second)
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 address: " + ip.String()) message(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)
}
}
+9 -23
View File
@@ -66,7 +66,6 @@ func main() {
waitSerial() waitSerial()
connectToAP() connectToAP()
displayIP()
for { for {
readConnection() readConnection()
@@ -122,42 +121,29 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
failMessage(err.Error())
}
func displayIP() { time.Sleep(2 * time.Second)
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 address: " + ip.String()) message(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)
}
}
+9 -27
View File
@@ -50,7 +50,6 @@ 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)
@@ -58,10 +57,7 @@ func main() {
laddr := &net.UDPAddr{Port: 2222} laddr := &net.UDPAddr{Port: 2222}
println("Dialing UDP connection...") println("Dialing UDP connection...")
conn, err := net.DialUDP("udp", laddr, raddr) conn, _ := net.DialUDP("udp", laddr, raddr)
if err != nil {
failMessage(err.Error())
}
for { for {
// send data // send data
@@ -78,42 +74,28 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
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 address: " + ip.String()) message(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)
}
}
+10 -25
View File
@@ -20,8 +20,8 @@ var (
pass string pass string
) )
// IP address of the "example.com" server. Replace with your own info. // IP address of the server aka "hub". Replace with your own info.
const server = "93.184.216.34" const server = "tinygo.org"
// 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,7 +63,6 @@ func main() {
waitSerial() waitSerial()
connectToAP() connectToAP()
displayIP()
for { for {
readConnection() readConnection()
@@ -124,42 +123,28 @@ 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)
var err error println("Connecting to " + ssid)
for i := 0; i < retriesBeforeFailure; i++ { err := adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second)
println("Connecting to " + ssid) if err != nil { // error connecting to AP
err = adaptor.ConnectToAccessPoint(ssid, pass, 10*time.Second) for {
if err == nil { println(err)
println("Connected.") time.Sleep(1 * time.Second)
return
} }
} }
// error connecting to AP println("Connected.")
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 address: " + ip.String()) message(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)
}
}
+19 -44
View File
@@ -27,11 +27,6 @@ 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() {
@@ -54,15 +49,31 @@ 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()
connectToAP() time.Sleep(2 * time.Second)
displayIP() println("Connecting to " + ssid)
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)
@@ -193,42 +204,6 @@ 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)
}
}
+1 -1
View File
@@ -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
) )
+2 -2
View File
@@ -5,8 +5,6 @@ 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=
@@ -34,5 +32,7 @@ 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=
+6 -46
View File
@@ -13,39 +13,6 @@ 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.
@@ -93,11 +60,11 @@ func (gps *Device) readNextSentence() (sentence string) {
gps.sentence.Reset() gps.sentence.Reset()
var b byte = ' ' var b byte = ' '
for b != startingDelimiter { for b != '$' {
b = gps.readNextByte() b = gps.readNextByte()
} }
for b != checksumDelimiter { for b != '*' {
gps.sentence.WriteByte(b) gps.sentence.WriteByte(b)
b = gps.readNextByte() b = gps.readNextByte()
} }
@@ -159,24 +126,17 @@ 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) < minimumNMEALength || sentence[0] != startingDelimiter || sentence[len(sentence)-3] != checksumDelimiter { if len(sentence) < 4 || sentence[0] != '$' || sentence[len(sentence)-3] != '*' {
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 := strings.ToUpper(hex.EncodeToString([]byte{cs})) checksum := hex.EncodeToString([]byte{cs})
if checksum != sentence[len(sentence)-2:len(sentence)] { if (checksum[0] != sentence[len(sentence)-2]) || (checksum[1] != sentence[len(sentence)-1]) {
return newGPSError(errInvalidNMEAChecksum, sentence, return errInvalidNMEAChecksum
"expected "+sentence[len(sentence)-2:len(sentence)]+
" got "+checksum)
} }
return nil return nil
+32 -63
View File
@@ -1,11 +1,19 @@
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 {
} }
@@ -43,65 +51,43 @@ 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, error) { func (parser *Parser) Parse(sentence string) (fix Fix, err error) {
var fix Fix
if sentence == "" { if sentence == "" {
return fix, errEmptyNMEASentence err = 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 {
return fix, errInvalidGGASentence err = errInvalidGGASentence
return
} }
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.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.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 {
return fix, errInvalidRMCSentence err = errInvalidRMCSentence
return
} }
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.Longitude = findLongitude(fields[5], fields[6])
fix.Latitude = findLatitude(fields[3], fields[4])
fix.Time = findTime(fields[1])
fix.Speed = findSpeed(fields[7]) fix.Speed = findSpeed(fields[7])
fix.Heading = findHeading(fields[8]) fix.Heading = findHeading(fields[8])
date := findDate(fields[9]) fix.Valid = (len(fields[2]) > 0 && fields[2][0:1] == "A")
fix.Time = fix.Time.AddDate(date.Year(), int(date.Month()), date.Day()) default:
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:
@@ -114,10 +100,7 @@ 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 := int64(0) ms, _ := strconv.ParseInt(val[7:10], 10, 16)
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
@@ -137,11 +120,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 {
dd := val[0:2] var dd = val[0:2]
mm := val[2:] var mm = val[2:]
d, _ := strconv.ParseFloat(dd, 32) var d, _ = strconv.ParseFloat(dd, 32)
m, _ := strconv.ParseFloat(mm, 32) var m, _ = strconv.ParseFloat(mm, 32)
v := float32(d + (m / 60)) var v = float32(d + (m / 60))
if hemi == "S" { if hemi == "S" {
v *= -1 v *= -1
} }
@@ -150,7 +133,7 @@ func findLatitude(val, hemi string) float32 {
return 0.0 return 0.0
} }
// findLongitude returns the longitude from an NMEA sentence: // findLatitude 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 {
@@ -179,20 +162,6 @@ 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 {
-103
View File
@@ -1,103 +0,0 @@
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))
}
-8
View File
@@ -3,9 +3,6 @@ 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
@@ -281,8 +278,3 @@ 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)
}
+16
View File
@@ -0,0 +1,16 @@
//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`)
}
}
+16
View File
@@ -0,0 +1,16 @@
//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`)
}
}
+16
View File
@@ -0,0 +1,16 @@
//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`)
}
}
+15
View File
@@ -0,0 +1,15 @@
//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`)
}
}
+16
View File
@@ -0,0 +1,16 @@
//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`)
}
}
+16
View File
@@ -0,0 +1,16 @@
//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`)
}
}
+13 -58
View File
@@ -5,21 +5,19 @@ 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 drivers.Rotation Rotation Rotation
DisplayInversion bool DisplayInversion bool
} }
type Device struct { type Device struct {
width int16 width int16
height int16 height int16
rotation drivers.Rotation rotation Rotation
driver driver driver driver
x0, x1 int16 // cached address window; prevents useless/expensive x0, x1 int16 // cached address window; prevents useless/expensive
@@ -138,12 +136,10 @@ 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) {
switch d.rotation { if d.rotation == 1 || d.rotation == 3 {
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.
@@ -160,18 +156,6 @@ 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()
@@ -257,41 +241,13 @@ func (d *Device) FillScreen(c color.RGBA) {
} }
} }
// Set the sleep mode for this LCD panel. When sleeping, the panel uses a lot // GetRotation returns the current rotation of the device
// less power. The LCD won't display an image anymore, but the memory contents func (d *Device) GetRotation() Rotation {
// 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
} }
// GetRotation returns the current rotation of the device. // SetRotation changes the rotation of the device (clock-wise)
// 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:
@@ -299,22 +255,21 @@ func (d *Device) SetRotation(rotation drivers.Rotation) error {
case Rotation90: case Rotation90:
madctl = MADCTL_MV | MADCTL_BGR madctl = MADCTL_MV | MADCTL_BGR
case Rotation180: case Rotation180:
madctl = MADCTL_MY | MADCTL_BGR | MADCTL_ML madctl = MADCTL_MY | MADCTL_BGR
case Rotation270: case Rotation270:
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR
case Rotation0Mirror: case Rotation0Mirror:
madctl = MADCTL_BGR madctl = MADCTL_BGR
case Rotation90Mirror: case Rotation90Mirror:
madctl = MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML madctl = MADCTL_MY | MADCTL_MV | MADCTL_BGR
case Rotation180Mirror: case Rotation180Mirror:
madctl = MADCTL_MX | MADCTL_MY | MADCTL_BGR | MADCTL_ML madctl = MADCTL_MX | MADCTL_MY | MADCTL_BGR
case Rotation270Mirror: case Rotation270Mirror:
madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR | MADCTL_ML madctl = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR
} }
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
+8 -12
View File
@@ -1,7 +1,5 @@
package ili9341 package ili9341
import "tinygo.org/x/drivers"
type Rotation uint8 type Rotation uint8
const ( const (
@@ -41,8 +39,6 @@ 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
@@ -81,13 +77,13 @@ const (
) )
const ( const (
Rotation0 = drivers.Rotation0 Rotation0 Rotation = 0
Rotation90 = drivers.Rotation90 // 90 degrees clock-wise rotation Rotation90 Rotation = 1 // 90 degrees clock-wise rotation
Rotation180 = drivers.Rotation180 Rotation180 Rotation = 2
Rotation270 = drivers.Rotation270 Rotation270 Rotation = 3
Rotation0Mirror = drivers.Rotation0Mirror Rotation0Mirror Rotation = 4
Rotation90Mirror = drivers.Rotation90Mirror Rotation90Mirror Rotation = 5
Rotation180Mirror = drivers.Rotation180Mirror Rotation180Mirror Rotation = 6
Rotation270Mirror = drivers.Rotation270Mirror Rotation270Mirror Rotation = 7
) )
-7
View File
@@ -76,10 +76,3 @@ const (
SyncPublic = iota SyncPublic = iota
SyncPrivate SyncPrivate
) )
const (
MHz_868_1 = 868100000
MHz_868_5 = 868500000
MHz_916_8 = 916800000
MHz_923_3 = 923300000
)
-138
View File
@@ -1,138 +0,0 @@
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
}
-20
View File
@@ -1,20 +0,0 @@
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
}
-253
View File
@@ -1,253 +0,0 @@
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
}
-38
View File
@@ -1,38 +0,0 @@
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
}
-185
View File
@@ -1,185 +0,0 @@
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
}
-49
View File
@@ -1,49 +0,0 @@
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
}
-49
View File
@@ -1,49 +0,0 @@
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
}
-16
View File
@@ -1,16 +0,0 @@
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
}

Some files were not shown because too many files have changed in this diff Show More